aboutsummaryrefslogtreecommitdiffstats
path: root/logserver/src/main/java/com/yahoo/logserver/handlers/archive/LogWriter.java
blob: 8d0eeb004fb48388a8769b3096a80859257c49c6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.logserver.handlers.archive;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.logging.Logger;

import com.yahoo.log.LogLevel;

/**
 * This class is not thread-safe.
 *
 * @author Bjorn Borud
 */
public class LogWriter extends Writer {
    private static final Logger log = Logger.getLogger(LogWriter.class.getName());

    private long bytesWritten = 0;
    private int generation = 0;
    private int maxSize = 20 * (1024 * 1024);
    private final int resumeLimit = 95;
    private final int resumeLimitSize = (maxSize * resumeLimit / 100);
    private File currentFile;
    private Writer writer;
    private final String prefix;

    public LogWriter(String prefix, int maxSize) throws IOException {
        this.prefix = prefix;
        this.maxSize = maxSize;
        writer = nextWriter();
    }

    /**
     * This is called when we want to rotate the output file to
     * start writing the next file.  There are two scenarios when
     * we do this:
     * <UL>
     * <LI> initial case, when we have no file
     * <LI> when we have filled the file and want to rotate it
     * </UL>
     */
    private Writer nextWriter() throws IOException {

        if (writer != null) {
            writer.close();
        }

        int maxAttempts = 1000;
        while (maxAttempts-- > 0) {
            String name = prefix + "-" + generation++;
            File f = new File(name);

            // make sure directory exists
            File dir = f.getParentFile();
            if (! dir.exists()) {
                dir.mkdirs();
            }

            // if compressed version exists we skip it
            if ((new File(name + ".gz").exists())
                    || (new File(name + ".bz2").exists())) {
                continue;
            }

            // if file does not exist we have a winner
            if (! f.exists()) {
                log.log(LogLevel.DEBUG, "nextWriter, new file: " + name);
                currentFile = f;
                bytesWritten = 0;
                return new FileWriter(f, true);
            }

            // just skip over directories for now
            if (! f.isFile()) {
                log.fine("nextWriter, " + name + " is a directory, skipping");
                continue;
            }

            // if the size is < resumeSizeLimit then we open it
            if (f.length() < resumeLimitSize) {
                log.fine("nextWriter, resuming " + name + ", length was " + f.length());
                currentFile = f;
                bytesWritten = f.length();
                return new FileWriter(f, true);
            } else {

                log.fine("nextWriter, not resuming " + name
                                 + " because it is bigger than "
                                 + resumeLimit
                                 + " percent of max");
            }
        }

        throw new RuntimeException("Unable to create next log file");
    }

    /**
     * Note that this method should not be used directly since
     * that would circumvent rotation when it grows past its
     * maximum size.  use the one that takes String instead.
     * <p>
     * <em>
     * (This is a class which is only used internally anyway)
     * </em>
     */
    public void write(char[] cbuff, int offset, int len) throws IOException {
        throw new RuntimeException("This method should not be used");
    }

    public void write(String str) throws IOException {
        if (writer == null) {
            writer = nextWriter();
        }

        bytesWritten += str.length();
        writer.write(str, 0, str.length());

        if (bytesWritten >= maxSize) {
            log.fine("logfile '"
                             + currentFile.getAbsolutePath()
                             + "' full, rotating");
            writer = nextWriter();
        }
    }


    public void flush() throws IOException {
        if (writer != null) {
            writer.flush();
        }
    }

    public void close() throws IOException {
        flush();
        if (writer != null) {
            writer.close();
            writer = null;
        }
    }
}