1 /**
2 * Logback: the reliable, generic, fast and flexible logging framework.
3 * Copyright (C) 1999-2011, QOS.ch. All rights reserved.
4 *
5 * This program and the accompanying materials are dual-licensed under
6 * either the terms of the Eclipse Public License v1.0 as published by
7 * the Eclipse Foundation
8 *
9 * or (per the licensee's choosing)
10 *
11 * under the terms of the GNU Lesser General Public License version 2.1
12 * as published by the Free Software Foundation.
13 */
14 package ch.qos.logback.core;
15
16 import java.io.File;
17 import java.io.IOException;
18 import java.nio.channels.FileChannel;
19 import java.nio.channels.FileLock;
20
21 import ch.qos.logback.core.recovery.ResilientFileOutputStream;
22 import ch.qos.logback.core.util.FileUtil;
23
24 /**
25 * FileAppender appends log events to a file.
26 *
27 * For more information about this appender, please refer to the online manual
28 * at http://logback.qos.ch/manual/appenders.html#FileAppender
29 *
30 * @author Ceki Gülcü
31 */
32 public class FileAppender<E> extends OutputStreamAppender<E> {
33
34 /**
35 * Append to or truncate the file? The default value for this variable is
36 * <code>true</code>, meaning that by default a <code>FileAppender</code> will
37 * append to an existing file and not truncate it.
38 */
39 protected boolean append = true;
40
41 /**
42 * The name of the active log file.
43 */
44 protected String fileName = null;
45
46 private boolean prudent = false;
47
48 /**
49 * As in most cases, the default constructor does nothing.
50 */
51 public FileAppender() {
52 }
53
54 /**
55 * The <b>File</b> property takes a string value which should be the name of
56 * the file to append to.
57 */
58 public void setFile(String file) {
59 if (file == null) {
60 fileName = file;
61 } else {
62 // Trim spaces from both ends. The users probably does not want
63 // trailing spaces in file names.
64 String val = file.trim();
65 fileName = val;
66 }
67 }
68
69 /**
70 * Returns the value of the <b>Append</b> property.
71 */
72 public boolean isAppend() {
73 return append;
74 }
75
76 /**
77 * This method is used by derived classes to obtain the raw file property.
78 * Regular users should not be calling this method.
79 *
80 * @return the value of the file property
81 */
82 final public String rawFileProperty() {
83 return fileName;
84 }
85
86 /**
87 * Returns the value of the <b>File</b> property.
88 *
89 * <p>
90 * This method may be overridden by derived classes.
91 *
92 */
93 public String getFile() {
94 return fileName;
95 }
96
97 /**
98 * If the value of <b>File</b> is not <code>null</code>, then
99 * {@link #openFile} is called with the values of <b>File</b> and
100 * <b>Append</b> properties.
101 */
102 public void start() {
103 int errors = 0;
104 if (getFile() != null) {
105 addInfo("File property is set to [" + fileName + "]");
106
107 if (prudent) {
108 if (isAppend() == false) {
109 setAppend(true);
110 addWarn("Setting \"Append\" property to true on account of \"Prudent\" mode");
111 }
112 }
113
114 try {
115 openFile(getFile());
116 } catch (java.io.IOException e) {
117 errors++;
118 addError("openFile(" + fileName + "," + append + ") call failed.", e);
119 }
120 } else {
121 errors++;
122 addError("\"File\" property not set for appender named [" + name + "].");
123 }
124 if (errors == 0) {
125 super.start();
126 }
127 }
128
129 /**
130 * <p>
131 * Sets and <i>opens</i> the file where the log output will go. The specified
132 * file must be writable.
133 *
134 * <p>
135 * If there was already an opened file, then the previous file is closed
136 * first.
137 *
138 * <p>
139 * <b>Do not use this method directly. To configure a FileAppender or one of
140 * its subclasses, set its properties one by one and then call start().</b>
141 *
142 * @param filename
143 * The path to the log file.
144 * @param append
145 * If true will append to fileName. Otherwise will truncate fileName.
146 * @param bufferedIO
147 * @param bufferSize
148 *
149 * @throws IOException
150 *
151 */
152 public void openFile(String file_name) throws IOException {
153 synchronized (lock) {
154 File file = new File(file_name);
155 if (FileUtil.isParentDirectoryCreationRequired(file)) {
156 boolean result = FileUtil.createMissingParentDirectories(file);
157 if (!result) {
158 addError("Failed to create parent directories for ["
159 + file.getAbsolutePath() + "]");
160 }
161 }
162
163 ResilientFileOutputStream resilientFos = new ResilientFileOutputStream(
164 file, append);
165 resilientFos.setContext(context);
166 setOutputStream(resilientFos);
167 }
168 }
169
170 /**
171 * @see #setPrudent(boolean)
172 *
173 * @return true if in prudent mode
174 */
175 public boolean isPrudent() {
176 return prudent;
177 }
178
179 /**
180 * When prudent is set to true, file appenders from multiple JVMs can safely
181 * write to the same file.
182 *
183 * @param prudent
184 */
185 public void setPrudent(boolean prudent) {
186 this.prudent = prudent;
187 }
188
189 public void setAppend(boolean append) {
190 this.append = append;
191 }
192
193 final private void safeWrite(E event) throws IOException {
194 ResilientFileOutputStream resilientFOS = (ResilientFileOutputStream) getOutputStream();
195 FileChannel fileChannel = resilientFOS.getChannel();
196 if (fileChannel == null) {
197 return;
198 }
199 FileLock fileLock = null;
200 try {
201 fileLock = fileChannel.lock();
202 long position = fileChannel.position();
203 long size = fileChannel.size();
204 if (size != position) {
205 fileChannel.position(size);
206 }
207 super.writeOut(event);
208 } finally {
209 if (fileLock != null) {
210 fileLock.release();
211 }
212 }
213 }
214
215 @Override
216 protected void writeOut(E event) throws IOException {
217 if (prudent) {
218 safeWrite(event);
219 } else {
220 super.writeOut(event);
221 }
222 }
223 }