forked from stevej/configgy
/
FileHandler.scala
168 lines (151 loc) · 4.85 KB
/
FileHandler.scala
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/*
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
*
* Licensed 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 net.lag.logging
import java.io.{File, FileOutputStream, OutputStreamWriter, Writer}
import java.text.SimpleDateFormat
import java.util.{Calendar, Date, logging => javalog}
sealed abstract class Policy
case object Never extends Policy
case object Hourly extends Policy
case object Daily extends Policy
case class Weekly(dayOfWeek: Int) extends Policy
/**
* A log handler that writes log entries into a file, and rolls this file
* at a requested interval (hourly, daily, or weekly).
*/
class FileHandler(val filename: String, val policy: Policy, formatter: Formatter,
val append: Boolean, val handleSighup: Boolean) extends Handler(formatter) {
private var stream: Writer = null
private var openTime: Long = 0
private var nextRollTime: Long = 0
var rotateCount = -1
openLog()
if (handleSighup) {
HandleSignal("HUP") { signal =>
val oldStream = stream
synchronized {
stream = openWriter()
}
try {
oldStream.close()
} catch { case _ => () }
}
}
def flush() = {
stream.flush()
}
def close() = {
flush()
try {
stream.close()
} catch { case _ => () }
}
private def openWriter() = {
val dir = new File(filename).getParentFile
if ((dir ne null) && !dir.exists) dir.mkdirs
new OutputStreamWriter(new FileOutputStream(filename, append), "UTF-8")
}
private def openLog() = {
stream = openWriter()
openTime = System.currentTimeMillis
nextRollTime = computeNextRollTime()
}
/**
* Compute the suffix for a rolled logfile, based on the roll policy.
*/
def timeSuffix(date: Date) = {
val dateFormat = policy match {
case Never => new SimpleDateFormat("yyyy")
case Hourly => new SimpleDateFormat("yyyyMMdd-HH")
case Daily => new SimpleDateFormat("yyyyMMdd")
case Weekly(_) => new SimpleDateFormat("yyyyMMdd")
}
dateFormat.setCalendar(formatter.calendar)
dateFormat.format(date)
}
/**
* Return the time (in absolute milliseconds) of the next desired
* logfile roll.
*/
def computeNextRollTime(now: Long): Long = {
val next = formatter.calendar.clone.asInstanceOf[Calendar]
next.setTimeInMillis(now)
next.set(Calendar.MILLISECOND, 0)
next.set(Calendar.SECOND, 0)
next.set(Calendar.MINUTE, 0)
policy match {
case Never =>
next.add(Calendar.YEAR, 100)
case Hourly =>
next.add(Calendar.HOUR_OF_DAY, 1)
case Daily =>
next.set(Calendar.HOUR_OF_DAY, 0)
next.add(Calendar.DAY_OF_MONTH, 1)
case Weekly(weekday) =>
next.set(Calendar.HOUR_OF_DAY, 0)
do {
next.add(Calendar.DAY_OF_MONTH, 1)
} while (next.get(Calendar.DAY_OF_WEEK) != weekday)
}
next.getTimeInMillis
}
def computeNextRollTime(): Long = computeNextRollTime(System.currentTimeMillis)
/**
* Delete files when "too many" have accumulated.
* This duplicates logrotate's "rotate count" option.
*/
private def removeOldFiles() = {
val rotateCountPlusOne = rotateCount + 1 // Because the new file is already open.
if (rotateCountPlusOne >= 1) {
val filesInLogDir = new File(filename).getParentFile().list()
val filteredFilesInLogDir = filesInLogDir.filter(f => f.startsWith(new File(filename).getName()))
if (filteredFilesInLogDir.length > rotateCount) {
for (i <- rotateCount.until(filteredFilesInLogDir.length)) {
new File(filteredFilesInLogDir(i)).delete()
}
}
}
}
def roll() = synchronized {
stream.close()
val n = filename.lastIndexOf('.')
val newFilename = if (n > 0) {
filename.substring(0, n) + "-" + timeSuffix(new Date(openTime)) + filename.substring(n)
} else {
filename + "-" + timeSuffix(new Date(openTime))
}
new File(filename).renameTo(new File(newFilename))
openLog()
removeOldFiles()
}
def publish(record: javalog.LogRecord) = {
try {
synchronized {
if (System.currentTimeMillis > nextRollTime) {
roll
}
}
val formattedLine = getFormatter.format(record)
synchronized {
stream.write(formattedLine)
stream.flush
}
} catch {
case e =>
System.err.println(Formatter.formatStackTrace(e, 30).mkString("\n"))
}
}
}