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
|
"""
Log messages and related utilities.
"""
import time
from warnings import warn
from pyrsistent import PClass, pmap_field
MESSAGE_TYPE_FIELD = "message_type"
TASK_UUID_FIELD = "task_uuid"
TASK_LEVEL_FIELD = "task_level"
TIMESTAMP_FIELD = "timestamp"
EXCEPTION_FIELD = "exception"
REASON_FIELD = "reason"
class Message(object):
"""
A log message.
Messages are basically dictionaries, mapping "fields" to "values". Field
names should not start with C{'_'}, as those are reserved for system use
(e.g. C{"_id"} is used by Elasticsearch for unique message identifiers and
may be auto-populated by logstash).
"""
# Overrideable for testing purposes:
_time = time.time
@classmethod
def new(_class, _serializer=None, **fields):
"""
Create a new L{Message}.
The keyword arguments will become the initial contents of the L{Message}.
@param _serializer: A positional argument, either C{None} or a
L{eliot._validation._MessageSerializer} with which a
L{eliot.ILogger} may choose to serialize the message. If you're
using L{eliot.MessageType} this will be populated for you.
@return: The new L{Message}
"""
warn(
"Message.new() is deprecated since 1.11.0, "
"use eliot.log_message() instead.",
DeprecationWarning,
stacklevel=2,
)
return _class(fields, _serializer)
@classmethod
def log(_class, **fields):
"""
Write a new L{Message} to the default L{Logger}.
The keyword arguments will become contents of the L{Message}.
"""
warn(
"Message.log() is deprecated since 1.11.0, "
"use Action.log() or eliot.log_message() instead.",
DeprecationWarning,
stacklevel=2,
)
_class(fields).write()
def __init__(self, contents, serializer=None):
"""
You can also use L{Message.new} to create L{Message} objects.
@param contents: The contents of this L{Message}, a C{dict} whose keys
must be C{str}, or text that has been UTF-8 encoded to
C{bytes}.
@param serializer: Either C{None}, or
L{eliot._validation._MessageSerializer} with which a
L{eliot.Logger} may choose to serialize the message. If you're
using L{eliot.MessageType} this will be populated for you.
"""
self._contents = contents.copy()
self._serializer = serializer
def bind(self, **fields):
"""
Return a new L{Message} with this message's contents plus the
additional given bindings.
"""
contents = self._contents.copy()
contents.update(fields)
return Message(contents, self._serializer)
def contents(self):
"""
Return a copy of L{Message} contents.
"""
return self._contents.copy()
def _timestamp(self):
"""
Return the current time.
"""
return self._time()
def write(self, logger=None, action=None):
"""
Write the message to the given logger.
This will additionally include a timestamp, the action context if any,
and any other fields.
Byte field names will be converted to Unicode.
@type logger: L{eliot.ILogger} or C{None} indicating the default one.
Should not be set if the action is also set.
@param action: The L{Action} which is the context for this message. If
C{None}, the L{Action} will be deduced from the current call stack.
"""
fields = dict(self._contents)
if "message_type" not in fields:
fields["message_type"] = ""
if self._serializer is not None:
fields["__eliot_serializer__"] = self._serializer
if action is None:
if logger is not None:
fields["__eliot_logger__"] = logger
log_message(**fields)
else:
action.log(**fields)
class WrittenMessage(PClass):
"""
A L{Message} that has been logged.
@ivar _logged_dict: The originally logged dictionary.
"""
_logged_dict = pmap_field((str, str), object)
@property
def timestamp(self):
"""
The Unix timestamp of when the message was logged.
"""
return self._logged_dict[TIMESTAMP_FIELD]
@property
def task_uuid(self):
"""
The UUID of the task in which the message was logged.
"""
return self._logged_dict[TASK_UUID_FIELD]
@property
def task_level(self):
"""
The L{TaskLevel} of this message appears within the task.
"""
return TaskLevel(level=self._logged_dict[TASK_LEVEL_FIELD])
@property
def contents(self):
"""
A C{PMap}, the message contents without Eliot metadata.
"""
return (
self._logged_dict.discard(TIMESTAMP_FIELD)
.discard(TASK_UUID_FIELD)
.discard(TASK_LEVEL_FIELD)
)
@classmethod
def from_dict(cls, logged_dictionary):
"""
Reconstruct a L{WrittenMessage} from a logged dictionary.
@param logged_dictionary: A C{PMap} representing a parsed log entry.
@return: A L{WrittenMessage} for that dictionary.
"""
return cls(_logged_dict=logged_dictionary)
def as_dict(self):
"""
Return the dictionary that was used to write this message.
@return: A C{dict}, as might be logged by Eliot.
"""
return self._logged_dict
# Import at end to deal with circular imports:
from ._action import log_message, TaskLevel
|