-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlog_device.rb
207 lines (190 loc) · 5.64 KB
/
log_device.rb
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# frozen_string_literal: true
require_relative 'period'
class Logger
# Device used for logging messages.
class LogDevice
include Period
attr_reader :dev
attr_reader :filename
include MonitorMixin
def initialize(log = nil, shift_age: nil, shift_size: nil, shift_period_suffix: nil, binmode: false)
@dev = @filename = @shift_age = @shift_size = @shift_period_suffix = nil
@binmode = binmode
mon_initialize
set_dev(log)
if @filename
@shift_age = shift_age || 7
@shift_size = shift_size || 1048576
@shift_period_suffix = shift_period_suffix || '%Y%m%d'
unless @shift_age.is_a?(Integer)
base_time = @dev.respond_to?(:stat) ? @dev.stat.mtime : Time.now
@next_rotate_time = next_rotate_time(base_time, @shift_age)
end
end
end
def write(message)
begin
synchronize do
if @shift_age and @dev.respond_to?(:stat)
begin
check_shift_log
rescue
warn("log shifting failed. #{$!}")
end
end
begin
@dev.write(message)
rescue
warn("log writing failed. #{$!}")
end
end
rescue Exception => ignored
warn("log writing failed. #{ignored}")
end
end
def close
begin
synchronize do
@dev.close rescue nil
end
rescue Exception
@dev.close rescue nil
end
end
def reopen(log = nil)
# reopen the same filename if no argument, do nothing for IO
log ||= @filename if @filename
if log
synchronize do
if @filename and @dev
@dev.close rescue nil # close only file opened by Logger
@filename = nil
end
set_dev(log)
end
end
self
end
private
def set_dev(log)
if log.respond_to?(:write) and log.respond_to?(:close)
@dev = log
if log.respond_to?(:path) and path = log.path
if File.exist?(path)
@filename = path
end
end
else
@dev = open_logfile(log)
@dev.sync = true
@dev.binmode if @binmode
@filename = log
end
end
def open_logfile(filename)
begin
File.open(filename, (File::WRONLY | File::APPEND))
rescue Errno::ENOENT
create_logfile(filename)
end
end
def create_logfile(filename)
begin
logdev = File.open(filename, (File::WRONLY | File::APPEND | File::CREAT | File::EXCL))
logdev.flock(File::LOCK_EX)
logdev.sync = true
logdev.binmode if @binmode
add_log_header(logdev)
logdev.flock(File::LOCK_UN)
rescue Errno::EEXIST
# file is created by another process
logdev = open_logfile(filename)
logdev.sync = true
end
logdev
end
def add_log_header(file)
file.write(
"# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName]
) if file.size == 0
end
def check_shift_log
if @shift_age.is_a?(Integer)
# Note: always returns false if '0'.
if @filename && (@shift_age > 0) && (@dev.stat.size > @shift_size)
lock_shift_log { shift_log_age }
end
else
now = Time.now
if now >= @next_rotate_time
@next_rotate_time = next_rotate_time(now, @shift_age)
lock_shift_log { shift_log_period(previous_period_end(now, @shift_age)) }
end
end
end
if /mswin|mingw|cygwin/ =~ RbConfig::CONFIG['host_os']
def lock_shift_log
yield
end
else
def lock_shift_log
retry_limit = 8
retry_sleep = 0.1
begin
File.open(@filename, File::WRONLY | File::APPEND) do |lock|
lock.flock(File::LOCK_EX) # inter-process locking. will be unlocked at closing file
if File.identical?(@filename, lock) and File.identical?(lock, @dev)
yield # log shifting
else
# log shifted by another process (i-node before locking and i-node after locking are different)
@dev.close rescue nil
@dev = open_logfile(@filename)
@dev.sync = true
end
end
rescue Errno::ENOENT
# @filename file would not exist right after #rename and before #create_logfile
if retry_limit <= 0
warn("log rotation inter-process lock failed. #{$!}")
else
sleep retry_sleep
retry_limit -= 1
retry_sleep *= 2
retry
end
end
rescue
warn("log rotation inter-process lock failed. #{$!}")
end
end
def shift_log_age
(@shift_age-3).downto(0) do |i|
if FileTest.exist?("#{@filename}.#{i}")
File.rename("#{@filename}.#{i}", "#{@filename}.#{i+1}")
end
end
@dev.close rescue nil
File.rename("#{@filename}", "#{@filename}.0")
@dev = create_logfile(@filename)
return true
end
def shift_log_period(period_end)
suffix = period_end.strftime(@shift_period_suffix)
age_file = "#{@filename}.#{suffix}"
if FileTest.exist?(age_file)
# try to avoid filename crash caused by Timestamp change.
idx = 0
# .99 can be overridden; avoid too much file search with 'loop do'
while idx < 100
idx += 1
age_file = "#{@filename}.#{suffix}.#{idx}"
break unless FileTest.exist?(age_file)
end
end
@dev.close rescue nil
File.rename("#{@filename}", age_file)
@dev = create_logfile(@filename)
return true
end
end
end