Project

Profile

Help

Download (6.94 KB) Statistics View on GitHub Reload from mirrored respository
| Branch: | Tag: | Revision:

github / src / log.py @ 96664dff

1 4946573d Vincent Le Goff
# 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 0f654c36 Vincent Le Goff
import sys
45 c3ed4a83 Vincent Le Goff
import threading
46 0f654c36 Vincent Le Goff
import traceback
47 4946573d Vincent Le Goff
48 1c05369a Vincent Le Goff
from ui.dialogs.bug import BugDialog
49
50 4946573d Vincent Le Goff
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 106ad7eb Vincent Le Goff
        # 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 4946573d Vincent Le Goff
    # 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 d0fd91d5 Vincent Le Goff
def get_date_formats():
141
    """Return the date formats in a dictionary."""
142 4946573d Vincent Le Goff
    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 d0fd91d5 Vincent Le Goff
    return formats
153
154
def begin():
155
    """Log the beginning of the session to every logger."""
156
    formats = get_date_formats()
157 4946573d Vincent Le Goff
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 d0fd91d5 Vincent Le Goff
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 4946573d Vincent Le Goff
# Prepare the different loggers
181
if not os.path.exists("logs"):
182
    os.mkdir("logs")
183
184 0f654c36 Vincent Le Goff
main = logger("")  # Main logger
185 45e9ba87 Vincent Le Goff
character = logger("character")  # Wizard logger
186 0f654c36 Vincent Le Goff
client = logger("client")  # Client logger
187
sharp = logger("sharp")  # SharpEngine logger
188 0af0ba77 Vincent Le Goff
task = logger("task")  # Task logger
189 0f654c36 Vincent Le Goff
ui = logger("ui")  # User Interface logger
190 20ef9d49 Vincent Le Goff
wizard = logger("wizard")  # Wizard logger
191 0f654c36 Vincent Le Goff
192
# Write a special exceptionhook
193
def excepthook(type, value, tb):
194 c3ed4a83 Vincent Le Goff
    """New except hook, the exception is logged."""
195 0f654c36 Vincent Le Goff
    message = 'Uncaught exception:\n'
196
    message += "".join(traceback.format_exception(type, value, tb))
197 c3ed4a83 Vincent Le Goff
    main.error(message.strip())
198 0f654c36 Vincent Le Goff
199 1c05369a Vincent Le Goff
    # Create the bug dialog
200
    dialog = BugDialog("".join(traceback.format_exception(
201
            type, value, tb)).strip("\n"))
202
    dialog.ShowModal()
203
204 0f654c36 Vincent Le Goff
sys.excepthook = excepthook
205 c3ed4a83 Vincent Le Goff
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()