root/cdat/trunk/libcdms/src/server/logger.py

Revision 2109, 6.8 kB (checked in by dubois, 7 years ago)

Rearrange repository; enable separate tarballs.

cdat/Tars -> exsrc
cdat/Packages -> Packages

Line 
1 # -*- Mode: Python; tab-width: 4 -*-
2
3 import asynchat
4 import socket
5 import string
6 import time         # these three are for the rotating logger
7 import os           # |
8 import stat         # v
9
10 #
11 # three types of log:
12 # 1) file
13 #    with optional flushing.  Also, one that rotates the log.
14 # 2) socket
15 #    dump output directly to a socket connection. [how do we
16 #    keep it open?]
17 # 3) syslog
18 #    log to syslog via tcp.  this is a per-line protocol.
19 #
20
21 #
22 # The 'standard' interface to a logging object is simply
23 # log_object.log (message)
24 #
25
26 # a file-like object that captures output, and
27 # makes sure to flush it always...  this could
28 # be connected to:
29 #  o    stdio file
30 #  o    low-level file
31 #  o    socket channel
32 #  o    syslog output...
33
34 class file_logger:
35                        
36         # pass this either a path or a file object.
37         def __init__ (self, file, flush=1, mode='a'):
38                 if type(file) == type(''):
39                         if (file == '-'):
40                                 import sys
41                                 self.file = sys.stdout
42                         else:
43                                 self.file = open (file, mode)
44                 else:
45                         self.file = file
46                 self.do_flush = flush
47
48         def __repr__ (self):
49                 return '<file logger: %s>' % self.file
50
51         def write (self, data):
52                 self.file.write (data)
53                 self.maybe_flush()
54                
55         def writeline (self, line):
56                 self.file.writeline (line)
57                 self.maybe_flush()
58                
59         def writelines (self, lines):
60                 self.file.writelines (lines)
61                 self.maybe_flush()
62
63         def maybe_flush (self):
64                 if self.do_flush:
65                         self.file.flush()
66
67         def flush (self):
68                 self.file.flush()
69
70         def softspace (self, *args):
71                 pass
72
73         def log (self, message):
74                 if message[-1] not in ('\r', '\n'):
75                         self.write (message + '\n')
76                 else:
77                         self.write (message)
78
79 # like a file_logger, but it must be attached to a filename.
80 # When the log gets too full, or a certain time has passed,
81 # it backs up the log and starts a new one.  Note that backing
82 # up the log is done via "mv" because anything else (cp, gzip)
83 # would take time, during which medusa would do nothing else.
84
85 class rotating_file_logger (file_logger):
86                        
87         # If freq is non-None we back up "daily", "weekly", or "monthly".
88         # Else if maxsize is non-None we back up whenever the log gets
89         # to big.  If both are None we never back up.
90         def __init__ (self, file, freq=None, maxsize=None, flush=1, mode='a'):
91                 self.filename = file
92                 self.mode = mode
93                 self.file = open (file, mode)
94                 self.freq = freq
95                 self.maxsize = maxsize
96                 self.rotate_when = self.next_backup(self.freq)
97                 self.do_flush = flush
98
99         def __repr__ (self):
100                 return '<rotating-file logger: %s>' % self.file
101
102         # We back up at midnight every 1) day, 2) monday, or 3) 1st of month
103         def next_backup (self, freq):
104                 (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
105                 if freq == 'daily':
106                         return time.mktime(yr,mo,day+1, 0,0,0, 0,0,-1)
107                 elif freq == 'weekly':
108                         return time.mktime(yr,mo,day-wd+7, 0,0,0, 0,0,-1)  # wd(monday)==0
109                 elif freq == 'monthly':
110                         return time.mktime(yr,mo+1,1, 0,0,0, 0,0,-1)
111                 else:
112                         return None                  # not a date-based backup
113
114         def maybe_flush (self):              # rotate first if necessary
115                 self.maybe_rotate()
116                 if self.do_flush:                # from file_logger()
117                         self.file.flush()
118
119         def maybe_rotate (self):
120                 if self.freq and time.time() > self.rotate_when:
121                         self.rotate()
122                         self.rotate_when = self.next_backup(self.freq)
123                 elif self.maxsize:               # rotate when we get too big
124                         try:
125                                 if os.stat(self.filename)[stat.ST_SIZE] > self.maxsize:
126                                         self.rotate()
127                         except os.error:             # file not found, probably
128                                 self.rotate()            # will create a new file
129
130         def rotate (self):
131                 (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
132                 try:
133                         self.file.close()
134                         newname = '%s.ends%04d%02d%02d' % (self.filename, yr, mo, day)
135                         try:
136                                 open(newname, "r").close()      # check if file exists
137                                 newname = newname + "-%02d%02d%02d" % (hr, min, sec)
138                         except:                             # YEARMODY is unique
139                                 pass
140                         os.rename(self.filename, newname)
141                         self.file = open(self.filename, self.mode)
142                 except:
143                         pass
144
145 # syslog is a line-oriented log protocol - this class would be
146 # appropriate for FTP or HTTP logs, but not for dumping stderr to.
147
148 # TODO: a simple safety wrapper that will ensure that the line sent
149 # to syslog is reasonable.
150
151 # TODO: async version of syslog_client: now, log entries use blocking
152 # send()
153
154 import m_syslog
155 syslog_logger = m_syslog.syslog_client
156
157 class syslog_logger (m_syslog.syslog_client):
158         def __init__ (self, address, facility='user'):
159                 m_syslog.syslog_client.__init__ (self, address)
160                 self.facility = m_syslog.facility_names[facility]
161                 self.address=address
162
163         def __repr__ (self):
164                 return '<syslog logger address=%s>' % (repr(self.address))
165
166         def log (self, message):
167                 m_syslog.syslog_client.log (
168                         self,
169                         message,
170                         facility=self.facility,
171                         priority=m_syslog.LOG_INFO
172                         )
173
174 # log to a stream socket, asynchronously
175
176 class socket_logger (asynchat.async_chat):
177
178         def __init__ (self, address):
179
180                 if type(address) == type(''):
181                         self.create_socket (socket.AF_UNIX, socket.SOCK_STREAM)
182                 else:
183                         self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
184
185                 self.connect (address)
186                 self.address = address
187                
188         def __repr__ (self):
189                 return '<socket logger: address=%s>' % (self.address)
190
191         def log (self, message):
192                 if message[-2:] != '\r\n':
193                         self.socket.push (message + '\r\n')
194                 else:
195                         self.socket.push (message)
196
197 # log to multiple places
198 class multi_logger:
199         def __init__ (self, loggers):
200                 self.loggers = loggers
201
202         def __repr__ (self):
203                 return '<multi logger: %s>' % (repr(self.loggers))
204
205         def log (self, message):
206                 for logger in self.loggers:
207                         logger.log (message)
208
209 class resolving_logger:
210         """Feed (ip, message) combinations into this logger to get a
211         resolved hostname in front of the message.  The message will not
212         be logged until the PTR request finishes (or fails)."""
213
214         def __init__ (self, resolver, logger):
215                 self.resolver = resolver
216                 self.logger = logger
217
218         class logger_thunk:
219                 def __init__ (self, message, logger):
220                         self.message = message
221                         self.logger = logger
222
223                 def __call__ (self, host, ttl, answer):
224                         if not answer:
225                                 answer = host
226                         self.logger.log ('%s:%s' % (answer, self.message))
227
228         def log (self, ip, message):
229                 self.resolver.resolve_ptr (
230                         ip,
231                         self.logger_thunk (
232                                 message,
233                                 self.logger
234                                 )
235                         )
236
237 class unresolving_logger:
238         "Just in case you don't want to resolve"
239         def __init__ (self, logger):
240                 self.logger = logger
241
242         def log (self, ip, message):
243                 self.logger.log ('%s:%s' % (ip, message))
244
245
246 def strip_eol (line):
247         while line and line[-1] in '\r\n':
248                 line = line[:-1]
249         return line
250
251 class tail_logger:
252         "Keep track of the last <size> log messages"
253         def __init__ (self, logger, size=500):
254                 self.size = size
255                 self.logger = logger
256                 self.messages = []
257
258         def log (self, message):
259                 self.messages.append (strip_eol (message))
260                 if len (self.messages) > self.size:
261                         del self.messages[0]
262                 self.logger.log (message)
Note: See TracBrowser for help on using the browser.