Skip to content

Commit 3bf3d3a

Browse files
committed
Merge branch 'master' into nanosecond-time
2 parents c8e2ca4 + 5c4dff1 commit 3bf3d3a

File tree

7 files changed

+123
-12
lines changed

7 files changed

+123
-12
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
/.tox
1010
/build
1111
/dist
12+
.idea/

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ python:
66
- "3.3"
77
- "3.4"
88
- "3.5"
9+
- "3.6"
10+
- "3.6-dev"
11+
- "3.7-dev"
12+
- "nightly"
913
# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
1014
install:
1115
- "pip install -e ."

README.rst

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ Event-Based Interface
110110
This API is a wrapper for `sender.FluentSender`.
111111

112112
First, you need to call ``sender.setup()`` to create global `sender.FluentSender` logger
113-
instance. This call needs to be called only once, at the beggining of
113+
instance. This call needs to be called only once, at the beginning of
114114
the application for example.
115115

116116
Initialization code of Event-Based API is below:
@@ -188,7 +188,7 @@ module.
188188
189189
logging.basicConfig(level=logging.INFO)
190190
l = logging.getLogger('fluent.test')
191-
h = handler.FluentHandler('app.follow', host='host', port=24224)
191+
h = handler.FluentHandler('app.follow', host='host', port=24224, buffer_overflow_handler=handler)
192192
formatter = handler.FluentRecordFormatter(custom_format)
193193
h.setFormatter(formatter)
194194
l.addHandler(h)
@@ -211,6 +211,18 @@ You can also customize formatter via logging.config.dictConfig
211211
212212
logging.config.dictConfig(conf['logging'])
213213
214+
You can inject your own custom proc to handle buffer overflow in the event of connection failure. This will mitigate the loss of data instead of simply throwing data away.
215+
216+
.. code:: python
217+
218+
import msgpack
219+
from io import BytesIO
220+
221+
def handler(pendings):
222+
unpacker = msgpack.Unpacker(BytesIO(pendings))
223+
for unpacked in unpacker:
224+
print(unpacked)
225+
214226
A sample configuration ``logging.yaml`` would be:
215227

216228
.. code:: python
@@ -242,17 +254,18 @@ A sample configuration ``logging.yaml`` would be:
242254
host: localhost
243255
port: 24224
244256
tag: test.logging
257+
buffer_overflow_handler: handler
245258
formatter: fluent_fmt
246259
level: DEBUG
247-
null:
260+
none:
248261
class: logging.NullHandler
249262
250263
loggers:
251264
amqp:
252-
handlers: [null]
265+
handlers: [none]
253266
propagate: False
254267
conf:
255-
handlers: [null]
268+
handlers: [none]
256269
propagate: False
257270
'': # root logger
258271
handlers: [console, fluent]
@@ -265,6 +278,21 @@ Testing
265278
Testing can be done using
266279
`nose <https://nose.readthedocs.org/en/latest/>`__.
267280

281+
Release
282+
-------
283+
284+
Need wheel package.
285+
286+
.. code:: sh
287+
288+
$ pip install wheel
289+
290+
After that, type following command:
291+
292+
.. code:: sh
293+
294+
$ python setup.py clean sdist bdist_wheel upload
295+
268296
Contributors
269297
------------
270298

fluent/handler.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ class FluentRecordFormatter(logging.Formatter, object):
2323
Best used with server storing data in an ElasticSearch cluster for example.
2424
2525
:param fmt: a dict with format string as values to map to provided keys.
26+
:param datefmt: strftime()-compatible date/time format string.
27+
:param style: (NOT USED)
28+
:param fill_missing_fmt_key: if True, do not raise a KeyError if the format
29+
key is not found. Put None if not found.s
2630
"""
27-
def __init__(self, fmt=None, datefmt=None):
31+
def __init__(self, fmt=None, datefmt=None, style='%', fill_missing_fmt_key=False):
2832
super(FluentRecordFormatter, self).__init__(None, datefmt)
2933

3034
if not fmt:
@@ -38,6 +42,8 @@ def __init__(self, fmt=None, datefmt=None):
3842

3943
self.hostname = socket.gethostname()
4044

45+
self.fill_missing_fmt_key = fill_missing_fmt_key
46+
4147
def format(self, record):
4248
# Only needed for python2.6
4349
if sys.version_info[0:2] <= (2, 6) and self.usesTime():
@@ -47,9 +53,18 @@ def format(self, record):
4753
super(FluentRecordFormatter, self).format(record)
4854
# Add ours
4955
record.hostname = self.hostname
56+
5057
# Apply format
51-
data = dict([(key, value % record.__dict__)
52-
for key, value in self._fmt_dict.items()])
58+
data = {}
59+
for key, value in self._fmt_dict.items():
60+
try:
61+
value = value % record.__dict__
62+
except KeyError as exc:
63+
value = None
64+
if not self.fill_missing_fmt_key:
65+
raise exc
66+
67+
data[key] = value
5368

5469
self._structuring(data, record)
5570
return data
@@ -73,7 +88,11 @@ def _structuring(self, data, record):
7388
self._add_dic(data, msg)
7489
elif isinstance(msg, basestring):
7590
try:
76-
self._add_dic(data, json.loads(str(msg)))
91+
json_msg = json.loads(str(msg))
92+
if isinstance(json_msg, dict):
93+
self._add_dic(data, json_msg)
94+
else:
95+
self._add_dic(data, {'message': str(json_msg)})
7796
except ValueError:
7897
msg = record.getMessage()
7998
self._add_dic(data, {'message': msg})
@@ -96,12 +115,14 @@ def __init__(self,
96115
host='localhost',
97116
port=24224,
98117
timeout=3.0,
99-
verbose=False):
118+
verbose=False,
119+
buffer_overflow_handler=None):
100120

101121
self.tag = tag
102122
self.sender = sender.FluentSender(tag,
103123
host=host, port=port,
104-
timeout=timeout, verbose=verbose)
124+
timeout=timeout, verbose=verbose,
125+
buffer_overflow_handler=buffer_overflow_handler)
105126
logging.Handler.__init__(self)
106127

107128
def emit(self, record):

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ cover-erase = 1
66
cover-branches = 1
77
cover-inclusive = 1
88
cover-min-percentage = 70
9+
[bdist_wheel]
10+
universal = 1

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
setup(
1414
name='fluent-logger',
15-
version='0.4.2',
15+
version='0.4.6',
1616
description=desc,
1717
long_description=open(README).read(),
1818
package_dir={'fluent': 'fluent'},

tests/test_handler.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,48 @@ def test_custom_fmt(self):
6262
self.assertTrue('lineno' in data[0][2])
6363
self.assertTrue('emitted_at' in data[0][2])
6464

65+
def test_custom_field_raise_exception(self):
66+
handler = fluent.handler.FluentHandler('app.follow', port=self._port)
67+
68+
logging.basicConfig(level=logging.INFO)
69+
log = logging.getLogger('fluent.test')
70+
handler.setFormatter(
71+
fluent.handler.FluentRecordFormatter(fmt={
72+
'name': '%(name)s',
73+
'custom_field': '%(custom_field)s'
74+
})
75+
)
76+
log.addHandler(handler)
77+
with self.assertRaises(KeyError):
78+
log.info({'sample': 'value'})
79+
log.removeHandler(handler)
80+
handler.close()
81+
82+
def test_custom_field_fill_missing_fmt_key_is_true(self):
83+
handler = fluent.handler.FluentHandler('app.follow', port=self._port)
84+
85+
logging.basicConfig(level=logging.INFO)
86+
log = logging.getLogger('fluent.test')
87+
handler.setFormatter(
88+
fluent.handler.FluentRecordFormatter(fmt={
89+
'name': '%(name)s',
90+
'custom_field': '%(custom_field)s'
91+
},
92+
fill_missing_fmt_key=True
93+
)
94+
)
95+
log.addHandler(handler)
96+
log.info({'sample': 'value'})
97+
log.removeHandler(handler)
98+
handler.close()
99+
100+
data = self.get_data()
101+
self.assertTrue('name' in data[0][2])
102+
self.assertEqual('fluent.test', data[0][2]['name'])
103+
self.assertTrue('custom_field' in data[0][2])
104+
# field defaults to none if not in log record
105+
self.assertIsNone(data[0][2]['custom_field'])
106+
65107
def test_json_encoded_message(self):
66108
handler = fluent.handler.FluentHandler('app.follow', port=self._port)
67109

@@ -104,6 +146,19 @@ def test_unstructured_formatted_message(self):
104146
self.assertTrue('message' in data[0][2])
105147
self.assertEqual('hello world, you!', data[0][2]['message'])
106148

149+
def test_number_string_simple_message(self):
150+
handler = fluent.handler.FluentHandler('app.follow', port=self._port)
151+
152+
logging.basicConfig(level=logging.INFO)
153+
log = logging.getLogger('fluent.test')
154+
handler.setFormatter(fluent.handler.FluentRecordFormatter())
155+
log.addHandler(handler)
156+
log.info("1")
157+
handler.close()
158+
159+
data = self.get_data()
160+
self.assertTrue('message' in data[0][2])
161+
107162
def test_non_string_simple_message(self):
108163
handler = fluent.handler.FluentHandler('app.follow', port=self._port)
109164

0 commit comments

Comments
 (0)