github / src / log.py @ 0ce7e35f
1 |
# Copyright (c) 2016, LE GOFF Vincent
|
---|---|
2 |
# All rights reserved.
|
3 |
|
4 |
# Redistribution and use in source and binary forms, with or without
|
5 |
# modification, are permitted provided that the following conditions are met:
|
6 |
|
7 |
# * Redistributions of source code must retain the above copyright notice, this
|
8 |
# list of conditions and the following disclaimer.
|
9 |
|
10 |
# * Redistributions in binary form must reproduce the above copyright notice,
|
11 |
# this list of conditions and the following disclaimer in the documentation
|
12 |
# and/or other materials provided with the distribution.
|
13 |
|
14 |
# * Neither the name of ytranslate nor the names of its
|
15 |
# contributors may be used to endorse or promote products derived from
|
16 |
# this software without specific prior written permission.
|
17 |
|
18 |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19 |
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20 |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21 |
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
22 |
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
23 |
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
24 |
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
25 |
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
26 |
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27 |
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28 |
|
29 |
"""File conaining the logging facility for CocoMUD.
|
30 |
|
31 |
Import this class from anywhere in the program to manipulate loggers. Top-level functions are used and return configured loggers, although furthering the configuration is still possible.
|
32 |
|
33 |
Example:
|
34 |
|
35 |
>>> from log import logger
|
36 |
>>> sharp_logger = logger("sharp")
|
37 |
>>> # Notice that, if the logger already exists, it will be returned
|
38 |
|
39 |
"""
|
40 |
|
41 |
from datetime import datetime |
42 |
import logging |
43 |
import os |
44 |
import sys |
45 |
import threading |
46 |
import traceback |
47 |
|
48 |
from ui.dialogs.bug import BugDialog |
49 |
|
50 |
class CustomFormatter(logging.Formatter): |
51 |
|
52 |
"""Special formatter to add hour and minute."""
|
53 |
|
54 |
def format(self, record): |
55 |
"""Add special placeholders for shorter messages."""
|
56 |
now = datetime.now() |
57 |
record.hour = now.hour |
58 |
record.minute = now.minute |
59 |
return logging.Formatter.format(self, record) |
60 |
|
61 |
|
62 |
loggers = {} |
63 |
|
64 |
def logger(name): |
65 |
"""Return an existing or new logger.
|
66 |
|
67 |
The name should be a string like 'sharp' to create the child
|
68 |
logger 'cocomud.sharp'. The log file 'logs/{name}.log' will be
|
69 |
created.
|
70 |
|
71 |
If the name is specified as an empty string, a main logger is
|
72 |
created. It will have the name 'cocomud' and will write both
|
73 |
in the 'logs/main.log' file and to the console (with an INFO
|
74 |
level).
|
75 |
|
76 |
"""
|
77 |
if not name: |
78 |
filename = os.path.join("logs", "main.log") |
79 |
name = "cocomud"
|
80 |
address = "main"
|
81 |
else:
|
82 |
address = name |
83 |
filename = os.path.join("logs", name + ".log") |
84 |
name = "cocomud." + name
|
85 |
|
86 |
if address in loggers: |
87 |
return loggers[address]
|
88 |
|
89 |
logger = logging.getLogger(name) |
90 |
logger.setLevel(logging.DEBUG) |
91 |
formatter = CustomFormatter( |
92 |
"%(hour)02d:%(minute)02d [%(levelname)s] %(message)s")
|
93 |
|
94 |
# If it's the main logger, create a stream handler
|
95 |
if name == "cocomud": |
96 |
handler = logging.StreamHandler() |
97 |
handler.setLevel(logging.INFO) |
98 |
logger.addHandler(handler) |
99 |
|
100 |
# Set a FileHandler for error messages
|
101 |
handler = logging.FileHandler(os.path.join("logs", "error.log"), |
102 |
encoding="utf-8")
|
103 |
handler.setLevel(logging.ERROR) |
104 |
handler.setFormatter(formatter) |
105 |
logger.addHandler(handler) |
106 |
|
107 |
# Create the file handler
|
108 |
handler = logging.FileHandler(filename, encoding="utf-8")
|
109 |
handler.setLevel(logging.DEBUG) |
110 |
handler.setFormatter(formatter) |
111 |
logger.addHandler(handler) |
112 |
loggers[address] = logger |
113 |
return logger
|
114 |
|
115 |
MONTHS = [ |
116 |
"January",
|
117 |
"February",
|
118 |
"March",
|
119 |
"April",
|
120 |
"May",
|
121 |
"June",
|
122 |
"July",
|
123 |
"August",
|
124 |
"September",
|
125 |
"October",
|
126 |
"November",
|
127 |
"December",
|
128 |
] |
129 |
|
130 |
WEEKDAYS = [ |
131 |
"Monday",
|
132 |
"Tuesday",
|
133 |
"Wednesday",
|
134 |
"Thursday",
|
135 |
"Friday",
|
136 |
"Saturday",
|
137 |
"Sunday",
|
138 |
] |
139 |
|
140 |
def get_date_formats(): |
141 |
"""Return the date formats in a dictionary."""
|
142 |
now = datetime.now() |
143 |
formats = { |
144 |
"year": now.year,
|
145 |
"month": MONTHS[now.month - 1], |
146 |
"weekday": WEEKDAYS[now.weekday()],
|
147 |
"day": now.day,
|
148 |
"hour": now.hour,
|
149 |
"minute": now.minute,
|
150 |
"second": now.second,
|
151 |
} |
152 |
return formats
|
153 |
|
154 |
def begin(): |
155 |
"""Log the beginning of the session to every logger."""
|
156 |
formats = get_date_formats() |
157 |
|
158 |
# Message to be sent
|
159 |
message = "CocoMUD started on {weekday}, {month} {day}, {year}"
|
160 |
message += " at {hour:>02}:{minute:>02}:{second:>02}"
|
161 |
message = message.format(**formats) |
162 |
for logger in loggers.values(): |
163 |
logger.propagate = False
|
164 |
logger.info(message) |
165 |
logger.propagate = True
|
166 |
|
167 |
def end(): |
168 |
"""Log the end of the session to every logger."""
|
169 |
formats = get_date_formats() |
170 |
|
171 |
# Message to be sent
|
172 |
message = "CocoMUD stopped on {weekday}, {month} {day}, {year}"
|
173 |
message += " at {hour:>02}:{minute:>02}:{second:>02}"
|
174 |
message = message.format(**formats) |
175 |
for logger in loggers.values(): |
176 |
logger.propagate = False
|
177 |
logger.info(message) |
178 |
logger.propagate = True
|
179 |
|
180 |
# Prepare the different loggers
|
181 |
if not os.path.exists("logs"): |
182 |
os.mkdir("logs")
|
183 |
|
184 |
main = logger("") # Main logger |
185 |
character = logger("character") # Wizard logger |
186 |
client = logger("client") # Client logger |
187 |
sharp = logger("sharp") # SharpEngine logger |
188 |
task = logger("task") # Task logger |
189 |
ui = logger("ui") # User Interface logger |
190 |
wizard = logger("wizard") # Wizard logger |
191 |
|
192 |
# Write a special exceptionhook
|
193 |
def excepthook(type, value, tb): |
194 |
"""New except hook, the exception is logged."""
|
195 |
message = 'Uncaught exception:\n'
|
196 |
message += "".join(traceback.format_exception(type, value, tb)) |
197 |
main.error(message.strip()) |
198 |
|
199 |
# Create the bug dialog
|
200 |
dialog = BugDialog("".join(traceback.format_exception(
|
201 |
type, value, tb)).strip("\n")) |
202 |
dialog.ShowModal() |
203 |
|
204 |
sys.excepthook = excepthook |
205 |
|
206 |
# Install the hook for other threads
|
207 |
def setup_thread_excepthook(): |
208 |
"""Install the sys.excepthook on all threads.
|
209 |
|
210 |
Workaround for `sys.excepthook` thread bug from:
|
211 |
http://bugs.python.org/issue1230540
|
212 |
|
213 |
Call once from the main thread before creating any threads.
|
214 |
|
215 |
"""
|
216 |
init_original = threading.Thread.__init__ |
217 |
|
218 |
def init(self, *args, **kwargs): |
219 |
init_original(self, *args, **kwargs)
|
220 |
run_original = self.run
|
221 |
|
222 |
def run_with_except_hook(*args2, **kwargs2): |
223 |
try:
|
224 |
run_original(*args2, **kwargs2) |
225 |
except Exception: |
226 |
sys.excepthook(*sys.exc_info()) |
227 |
|
228 |
self.run = run_with_except_hook
|
229 |
|
230 |
threading.Thread.__init__ = init |
231 |
|
232 |
setup_thread_excepthook() |