Project

Profile

Help

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

github / src / log.py @ 05b173a7

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()
(12-12/20)