Skip to content

Commit 5033ed1

Browse files
committed
Merge branch 'EvaSDK/allow-formatter-customizations'
Conflicts: README.md
2 parents 822eab7 + 70d68cb commit 5033ed1

File tree

5 files changed

+128
-19
lines changed

5 files changed

+128
-19
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
language: python
22
python:
3-
- "2.6"
43
- "2.7"
54
- "3.2"
65
- "3.3"

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ from fluent import handler
7676

7777
logging.basicConfig(level=logging.INFO)
7878
l = logging.getLogger('fluent.test')
79-
l.addHandler(handler.FluentHandler('app.follow', host='host', port=24224))
79+
h = handler.FluentHandler('app.follow', host='host', port=24224)
80+
h.setFormatter(handler.FluentRecordFormatter())
81+
l.addHandler(h)
8082
l.info({
8183
'from': 'userA',
8284
'to': 'userB'

fluent/handler.py

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import logging
44
import socket
5+
import sys
56

67
try:
78
import simplejson as json
@@ -16,35 +17,65 @@
1617
from fluent import sender
1718

1819

19-
class FluentRecordFormatter(object):
20-
def __init__(self):
20+
class FluentRecordFormatter(logging.Formatter, object):
21+
""" A structured formatter for Fluent.
22+
23+
Best used with server storing data in an ElasticSearch cluster for example.
24+
25+
:param fmt: a dict with format string as values to map to provided keys.
26+
"""
27+
def __init__(self, fmt=None, datefmt=None):
28+
super(FluentRecordFormatter, self).__init__(None, datefmt)
29+
30+
if not fmt:
31+
self._fmt_dict = {
32+
'sys_host': '%(hostname)s',
33+
'sys_name': '%(name)s',
34+
'sys_module': '%(module)s',
35+
}
36+
else:
37+
self._fmt_dict = fmt
38+
2139
self.hostname = socket.gethostname()
2240

2341
def format(self, record):
24-
data = {'sys_host': self.hostname,
25-
'sys_name': record.name,
26-
'sys_module': record.module,
27-
# 'sys_lineno': record.lineno,
28-
# 'sys_levelno': record.levelno,
29-
# 'sys_levelname': record.levelname,
30-
# 'sys_filename': record.filename,
31-
# 'sys_funcname': record.funcName,
32-
# 'sys_exc_info': record.exc_info,
33-
}
34-
# if 'sys_exc_info' in data and data['sys_exc_info']:
35-
# data['sys_exc_info'] = self.formatException(data['sys_exc_info'])
42+
# Only needed for python2.6
43+
if sys.version_info[0:2] <= (2, 6) and self.usesTime():
44+
record.asctime = self.formatTime(record, self.datefmt)
45+
46+
# Compute attributes handled by parent class.
47+
super(FluentRecordFormatter, self).format(record)
48+
# Add ours
49+
record.hostname = self.hostname
50+
# Apply format
51+
data = dict([(key, value % record.__dict__)
52+
for key, value in self._fmt_dict.items()])
3653

3754
self._structuring(data, record.msg)
3855
return data
3956

57+
def usesTime(self):
58+
return any([value.find('%(asctime)') >= 0
59+
for value in self._fmt_dict.values()])
60+
4061
def _structuring(self, data, msg):
62+
""" Melds `msg` into `data`.
63+
64+
:param data: dictionary to be sent to fluent server
65+
:param msg: :class:`LogRecord`'s message to add to `data`.
66+
`msg` can be a simple string for backward compatibility with
67+
:mod:`logging` framework, a JSON encoded string or a dictionary
68+
that will be merged into dictionary generated in :meth:`format.
69+
"""
4170
if isinstance(msg, dict):
4271
self._add_dic(data, msg)
43-
elif isinstance(msg, str):
72+
elif isinstance(msg, basestring):
4473
try:
4574
self._add_dic(data, json.loads(str(msg)))
4675
except ValueError:
47-
pass
76+
self._add_dic(data, {'message': msg})
77+
else:
78+
self._add_dic(data, {'message': msg})
4879

4980
@staticmethod
5081
def _add_dic(data, dic):

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.3.5',
15+
version='0.4.0.dev',
1616
description=desc,
1717
long_description=open(README).read(),
1818
package_dir={'fluent': 'fluent'},

tests/test_handler.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,80 @@ def test_simple(self):
4444
eq('userB', data[0][2]['to'])
4545
self.assertTrue(data[0][1])
4646
self.assertTrue(isinstance(data[0][1], int))
47+
48+
def test_custom_fmt(self):
49+
handler = fluent.handler.FluentHandler('app.follow', port=self._port)
50+
51+
logging.basicConfig(level=logging.INFO)
52+
log = logging.getLogger('fluent.test')
53+
handler.setFormatter(
54+
fluent.handler.FluentRecordFormatter(fmt={
55+
'name': '%(name)s',
56+
'lineno': '%(lineno)d',
57+
'emitted_at': '%(asctime)s',
58+
})
59+
)
60+
log.addHandler(handler)
61+
log.info({'sample': 'value'})
62+
handler.close()
63+
64+
data = self.get_data()
65+
self.assertTrue('name' in data[0][2])
66+
self.assertEqual('fluent.test', data[0][2]['name'])
67+
self.assertTrue('lineno' in data[0][2])
68+
self.assertTrue('emitted_at' in data[0][2])
69+
70+
def test_json_encoded_message(self):
71+
handler = fluent.handler.FluentHandler('app.follow', port=self._port)
72+
73+
logging.basicConfig(level=logging.INFO)
74+
log = logging.getLogger('fluent.test')
75+
handler.setFormatter(fluent.handler.FluentRecordFormatter())
76+
log.addHandler(handler)
77+
log.info('{"key": "hello world!", "param": "value"}')
78+
handler.close()
79+
80+
data = self.get_data()
81+
self.assertTrue('key' in data[0][2])
82+
self.assertEqual('hello world!', data[0][2]['key'])
83+
84+
def test_unstructured_message(self):
85+
handler = fluent.handler.FluentHandler('app.follow', port=self._port)
86+
87+
logging.basicConfig(level=logging.INFO)
88+
log = logging.getLogger('fluent.test')
89+
handler.setFormatter(fluent.handler.FluentRecordFormatter())
90+
log.addHandler(handler)
91+
log.info('hello world')
92+
handler.close()
93+
94+
data = self.get_data()
95+
self.assertTrue('message' in data[0][2])
96+
self.assertEqual('hello world', data[0][2]['message'])
97+
98+
def test_non_string_simple_message(self):
99+
handler = fluent.handler.FluentHandler('app.follow', port=self._port)
100+
101+
logging.basicConfig(level=logging.INFO)
102+
log = logging.getLogger('fluent.test')
103+
handler.setFormatter(fluent.handler.FluentRecordFormatter())
104+
log.addHandler(handler)
105+
log.info(42)
106+
handler.close()
107+
108+
data = self.get_data()
109+
self.assertTrue('message' in data[0][2])
110+
111+
def test_non_string_dict_message(self):
112+
handler = fluent.handler.FluentHandler('app.follow', port=self._port)
113+
114+
logging.basicConfig(level=logging.INFO)
115+
log = logging.getLogger('fluent.test')
116+
handler.setFormatter(fluent.handler.FluentRecordFormatter())
117+
log.addHandler(handler)
118+
log.info({42: 'root'})
119+
handler.close()
120+
121+
data = self.get_data()
122+
# For some reason, non-string keys are ignored
123+
self.assertFalse(42 in data[0][2])

0 commit comments

Comments
 (0)