Skip to content

Commit 3a0f692

Browse files
authored
Merge pull request #8 from HubSpot/js_file_length
Don't call file.length() for every append if we can avoid it
2 parents 5a9ae8f + 995ac97 commit 3a0f692

File tree

10 files changed

+111
-8
lines changed

10 files changed

+111
-8
lines changed

logback-core/src/main/java/ch/qos/logback/core/FileAppender.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.io.OutputStream;
2122
import java.nio.channels.FileChannel;
2223
import java.nio.channels.FileLock;
2324
import java.util.Map;
@@ -239,6 +240,20 @@ public void setBufferSize(FileSize bufferSize) {
239240
this.bufferSize = bufferSize;
240241
}
241242

243+
/**
244+
* Returns the current position in the current file, as counted by
245+
* {@link ch.qos.logback.core.recovery.ByteCountingOutputStream}, or zero if no file has been opened.
246+
*/
247+
protected long getCurrentFilePosition() {
248+
OutputStream outputStream = getOutputStream();
249+
if (outputStream == null) {
250+
return 0;
251+
} else {
252+
// we already cast to a ResilientFileOutputStream in #safeWrite()
253+
return ((ResilientFileOutputStream) outputStream).getCount();
254+
}
255+
}
256+
242257
@Override
243258
protected void writeOut(E event) throws IOException {
244259
if (prudent) {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package ch.qos.logback.core.recovery;
2+
3+
import java.io.FilterOutputStream;
4+
import java.io.IOException;
5+
import java.io.OutputStream;
6+
7+
public class ByteCountingOutputStream extends FilterOutputStream {
8+
9+
private long count;
10+
11+
public ByteCountingOutputStream(OutputStream out) {
12+
super(out);
13+
}
14+
15+
public long getByteCount() {
16+
return count;
17+
}
18+
19+
@Override
20+
public void write(byte[] b, int off, int len) throws IOException {
21+
out.write(b, off, len);
22+
count += len;
23+
}
24+
25+
@Override
26+
public void write(int b) throws IOException {
27+
out.write(b);
28+
count++;
29+
}
30+
31+
@Override
32+
public void close() throws IOException {
33+
out.close();
34+
}
35+
}

logback-core/src/main/java/ch/qos/logback/core/recovery/ResilientFileOutputStream.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@ public class ResilientFileOutputStream extends ResilientOutputStreamBase {
2020

2121
private File file;
2222
private FileOutputStream fos;
23+
private ByteCountingOutputStream countingOutputStream;
24+
private long originalFileLength;
2325

2426
public ResilientFileOutputStream(File file, boolean append, long bufferSize) throws FileNotFoundException {
2527
this.file = file;
28+
this.originalFileLength = append ? getFileLength(file) : 0;
2629
fos = new FileOutputStream(file, append);
27-
this.os = new BufferedOutputStream(fos, (int) bufferSize);
30+
countingOutputStream = new ByteCountingOutputStream(new BufferedOutputStream(fos, (int) bufferSize));
31+
this.os = countingOutputStream;
2832
this.presumedClean = true;
2933
}
3034

@@ -39,21 +43,35 @@ public File getFile() {
3943
return file;
4044
}
4145

46+
public long getCount() {
47+
return originalFileLength + (countingOutputStream == null ? 0 : countingOutputStream.getByteCount());
48+
}
49+
4250
@Override
4351
String getDescription() {
4452
return "file [" + file + "]";
4553
}
4654

4755
@Override
4856
OutputStream openNewOutputStream() throws IOException {
57+
originalFileLength = getFileLength(file);
4958
// see LOGBACK-765
5059
fos = new FileOutputStream(file, true);
51-
return new BufferedOutputStream(fos);
60+
countingOutputStream = new ByteCountingOutputStream(new BufferedOutputStream(fos));
61+
return countingOutputStream;
5262
}
5363

5464
@Override
5565
public String toString() {
5666
return "c.q.l.c.recovery.ResilientFileOutputStream@" + System.identityHashCode(this);
5767
}
5868

69+
private static long getFileLength(File file) {
70+
try {
71+
return file.length();
72+
} catch (Exception ignored) {
73+
// file doesn't exist or we don't have permissions
74+
return 0L;
75+
}
76+
}
5977
}

logback-core/src/main/java/ch/qos/logback/core/rolling/DefaultTimeBasedFileNamingAndTriggeringPolicy.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ public void start() {
4343
started = true;
4444
}
4545

46+
public boolean isTriggeringEvent(final File activeFile, final E event, long currentFilePosition) {
47+
return isTriggeringEvent(activeFile, event);
48+
}
49+
4650
public boolean isTriggeringEvent(File activeFile, final E event) {
4751
long currentTime = getCurrentTime();
4852
long localNextCheck = atomicNextCheck.get();

logback-core/src/main/java/ch/qos/logback/core/rolling/RollingFileAppender.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ protected void subAppend(E event) {
242242

243243
triggeringPolicyLock.lock();
244244
try {
245-
if (triggeringPolicy.isTriggeringEvent(currentlyActiveFile, event)) {
245+
if (triggeringPolicy.isTriggeringEvent(currentlyActiveFile, event, getCurrentFilePosition())) {
246246
rollover();
247247
}
248248
} finally {

logback-core/src/main/java/ch/qos/logback/core/rolling/SizeAndTimeBasedFNATP.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,11 @@ void computeCurrentPeriodsHighestCounterValue(final String stemRegex) {
141141

142142
@Override
143143
public boolean isTriggeringEvent(File activeFile, final E event) {
144+
return isTriggeringEvent(activeFile, event, -1);
145+
}
144146

147+
@Override
148+
public boolean isTriggeringEvent(final File activeFile, final E event, long currentFilePosition) {
145149
long currentTime = getCurrentTime();
146150
long localNextCheck = atomicNextCheck.get();
147151

@@ -158,10 +162,10 @@ public boolean isTriggeringEvent(File activeFile, final E event) {
158162
return true;
159163
}
160164

161-
return checkSizeBasedTrigger(activeFile, currentTime);
165+
return checkSizeBasedTrigger(activeFile, currentTime, currentFilePosition);
162166
}
163167

164-
private boolean checkSizeBasedTrigger(File activeFile, long currentTime) {
168+
private boolean checkSizeBasedTrigger(File activeFile, long currentTime, long currentFilePosition) {
165169
// next check for roll-over based on size
166170
if (invocationGate.isTooSoon(currentTime)) {
167171
return false;
@@ -175,7 +179,8 @@ private boolean checkSizeBasedTrigger(File activeFile, long currentTime) {
175179
addWarn("maxFileSize = null");
176180
return false;
177181
}
178-
if (activeFile.length() >= maxFileSize.getSize()) {
182+
long activeFileLength = currentFilePosition >= 0 ? currentFilePosition : activeFile.length();
183+
if (activeFileLength >= maxFileSize.getSize()) {
179184

180185
elapsedPeriodsFileName = tbrp.fileNamePatternWithoutCompSuffix.convertMultipleArguments(dateInCurrentPeriod,
181186
currentPeriodsCounter);

logback-core/src/main/java/ch/qos/logback/core/rolling/SizeBasedTriggeringPolicy.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,17 @@ public void start() {
5454
}
5555

5656

57-
public boolean isTriggeringEvent(final File activeFile, final E event) {
57+
@Override
58+
public boolean isTriggeringEvent(File activeFile, E event, long currentFilePosition) {
5859
long now = System.currentTimeMillis();
5960
if (invocationGate.isTooSoon(now))
6061
return false;
62+
long activeFileLength = currentFilePosition >= 0 ? currentFilePosition : activeFile.length();
63+
return (activeFileLength >= maxFileSize.getSize());
64+
}
6165

62-
return (activeFile.length() >= maxFileSize.getSize());
66+
public boolean isTriggeringEvent(final File activeFile, final E event) {
67+
return isTriggeringEvent(activeFile, event, -1);
6368
}
6469

6570
public FileSize getMaxFileSize() {

logback-core/src/main/java/ch/qos/logback/core/rolling/TimeBasedRollingPolicy.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,11 @@ public String getActiveFileName() {
227227
}
228228
}
229229

230+
@Override
231+
public boolean isTriggeringEvent(File activeFile, E event, long currentFilePosition) {
232+
return timeBasedFileNamingAndTriggeringPolicy.isTriggeringEvent(activeFile, event, currentFilePosition);
233+
}
234+
230235
/**
231236
* Delegates to the underlying timeBasedFileNamingAndTriggeringPolicy.
232237
*

logback-core/src/main/java/ch/qos/logback/core/rolling/TriggeringPolicy.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,6 @@ public interface TriggeringPolicy<E> extends LifeCycle {
3535
* @return true if a roll-over should occur.
3636
*/
3737
boolean isTriggeringEvent(final File activeFile, final E event);
38+
39+
boolean isTriggeringEvent(final File activeFile, final E event, long currentFilePosition);
3840
}

logback-core/src/test/java/ch/qos/logback/core/recovery/ResilientOutputStreamTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
package ch.qos.logback.core.recovery;
1515

16+
import static org.junit.jupiter.api.Assertions.assertEquals;
1617
import static org.mockito.Mockito.spy;
1718
import static org.mockito.Mockito.verify;
1819

@@ -51,15 +52,28 @@ public void verifyRecuperationAfterFailure() throws Exception {
5152

5253
spy.write("a".getBytes());
5354
spy.flush();
55+
assertEquals(1, spy.getCount());
5456

5557
spy.getChannel().close();
5658
spy.write("b".getBytes());
5759
spy.flush();
60+
// we have 2 in our countingoutput stream
61+
// but the 'b' write failed due to the channel closing
62+
assertEquals(2, spy.getCount());
5863
Thread.sleep(RecoveryCoordinator.BACKOFF_COEFFICIENT_MIN + 10);
5964
spy.write("c".getBytes());
6065
spy.flush();
66+
67+
// since we recovered the output stream, we recomputed
68+
// our count from the length of the file. both b and c were lost.
69+
assertEquals(1, spy.getCount());
6170
verify(spy).openNewOutputStream();
6271

72+
Thread.sleep(RecoveryCoordinator.BACKOFF_COEFFICIENT_MIN + 10);
73+
spy.write("d".getBytes());
74+
spy.flush();
75+
// the 'd' write succeeds, so we have 2 bytes written
76+
assertEquals(2, spy.getCount());
6377
}
6478

6579
}

0 commit comments

Comments
 (0)