Skip to content

Commit 76fcb16

Browse files
committed
Initial commit
0 parents  commit 76fcb16

File tree

5 files changed

+332
-0
lines changed

5 files changed

+332
-0
lines changed

LICENSE

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright (c) 2015 Sebastien Estienne <[email protected]>
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a
4+
copy of this software and associated documentation files (the
5+
"Software"), to deal in the Software without restriction, including
6+
without limitation the rights to use, copy, modify, merge, publish,
7+
distribute, sublicense, and/or sell copies of the Software, and to
8+
permit persons to whom the Software is furnished to do so, subject to
9+
the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included
12+
in all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.md

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
JSON logging for Python [![PyPi version](https://pypip.in/v/json-logging-py/badge.png)](https://crate.io/packages/json-logging-py/)
2+
============
3+
4+
This library provides Python logging formatters to output JSON, 2 formatters are specific for Logstash message format version 0 or 1.
5+
6+
Installation
7+
============
8+
9+
Using pip:
10+
11+
pip install json-logging-py
12+
13+
From source:
14+
15+
python setup.py install
16+
17+
Usage
18+
=====
19+
20+
The name of the library is `jsonlogging`, it provides 3 formatters:
21+
22+
### JSONFormatter
23+
24+
{
25+
"tags": [
26+
"env=prod",
27+
"role=www"
28+
],
29+
"timestamp": "2015-09-22T22:40:56.178715Z",
30+
"level": "ERROR",
31+
"host": "server-01.example.com",
32+
"path": "example.py",
33+
"message": "hello world!",
34+
"logger": "root"
35+
}
36+
37+
### LogstashFormatterV0
38+
39+
{
40+
"@source": "JSON://server-01.example.com/example.py",
41+
"@source_host": "server-01.example.com",
42+
"@message": "hello world!",
43+
"@tags": [
44+
"env=prod",
45+
"role=www"
46+
],
47+
"@fields": {
48+
"logger": "root",
49+
"levelname": "ERROR"
50+
},
51+
"@timestamp": "2015-09-22T22:42:02.094525Z",
52+
"@source_path": "example.py",
53+
"@type": "JSON"
54+
}
55+
56+
### LogstashFormatterV1
57+
58+
{
59+
"host": "server-01.example.com",
60+
"logger": "root",
61+
"type": "JSON",
62+
"tags": [
63+
"env=prod",
64+
"role=www"
65+
],
66+
"path": "example.py",
67+
"@timestamp": "2015-09-22T22:43:11.966558Z",
68+
"@version": 1,
69+
"message": "hello world!",
70+
"levelname": "ERROR"
71+
}
72+
73+
### Python example
74+
75+
import logging
76+
import jsonlogging
77+
78+
79+
logger = logging.getLogger()
80+
81+
logHandler = logging.StreamHandler()
82+
83+
# You can also use LogstashFormatterV0 or LogstashFormatterV1
84+
formatter = jsonlogging.JSONFormatter(
85+
hostname="server-01.example.com"
86+
tags=["env=prod", "role=www"],
87+
indent=4)
88+
logHandler.setFormatter(formatter)
89+
90+
logger.addHandler(logHandler)
91+
92+
# You can pass additional tags
93+
logger.error('hello world!', extra={"tags": ["hello=world"]})

example.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import logging
2+
import jsonlogging
3+
4+
5+
logger = logging.getLogger()
6+
7+
logHandler = logging.StreamHandler()
8+
9+
# You can also use LogstashFormatterV0 or LogstashFormatterV1
10+
formatter = jsonlogging.JSONFormatter(
11+
hostname="server-01.example.com",
12+
tags=["env=prod", "role=www"],
13+
indent=4)
14+
logHandler.setFormatter(formatter)
15+
16+
logger.addHandler(logHandler)
17+
18+
logger.error('hello world!', extra={"tags": ["hello=world"]})

jsonlogging.py

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import traceback
2+
import logging
3+
import socket
4+
import sys
5+
from datetime import datetime
6+
try:
7+
import simplejson as json
8+
except ImportError:
9+
import json
10+
11+
12+
class JSONFormatter(logging.Formatter):
13+
14+
def __init__(self, tags=None, hostname=None, fqdn=False, message_type='JSON', indent=None):
15+
self.message_type = message_type
16+
self.tags = tags if tags is not None else []
17+
self.extra_tags = []
18+
self.indent = indent
19+
20+
if hostname:
21+
self.host = hostname
22+
elif fqdn:
23+
self.host = socket.getfqdn()
24+
else:
25+
self.host = socket.gethostname()
26+
27+
def get_extra_fields(self, record):
28+
# The list contains all the attributes listed in
29+
# http://docs.python.org/library/logging.html#logrecord-attributes
30+
skip_list = [
31+
'asctime', 'created', 'exc_info', 'exc_text', 'filename', 'args',
32+
'funcName', 'id', 'levelname', 'levelno', 'lineno', 'module', 'msg',
33+
'msecs', 'msecs', 'message', 'name', 'pathname', 'process',
34+
'processName', 'relativeCreated', 'thread', 'threadName', 'extra']
35+
36+
if sys.version_info < (3, 0):
37+
easy_types = (basestring, bool, dict, float, int, list, type(None))
38+
else:
39+
easy_types = (str, bool, dict, float, int, list, type(None))
40+
41+
fields = {}
42+
43+
if record.args:
44+
fields['msg'] = record.msg
45+
46+
self.extra_tags = []
47+
for key, value in record.__dict__.items():
48+
if key not in skip_list:
49+
if key == 'tags' and isinstance(value, list):
50+
self.extra_tags = value
51+
elif isinstance(value, easy_types):
52+
fields[key] = value
53+
else:
54+
fields[key] = repr(value)
55+
56+
return fields
57+
58+
def get_debug_fields(self, record):
59+
if record.exc_info:
60+
exc_info = self.format_exception(record.exc_info)
61+
else:
62+
exc_info = record.exc_text
63+
return {
64+
'exc_info': exc_info,
65+
'filename': record.filename,
66+
'lineno': record.lineno,
67+
}
68+
69+
@classmethod
70+
def format_source(cls, message_type, host, path):
71+
return "%s://%s/%s" % (message_type, host, path)
72+
73+
@classmethod
74+
def format_timestamp(cls, time):
75+
return datetime.utcfromtimestamp(time).isoformat() + 'Z'
76+
77+
@classmethod
78+
def format_exception(cls, exc_info):
79+
return ''.join(traceback.format_exception(*exc_info)) if exc_info else ''
80+
81+
@classmethod
82+
def serialize(cls, message, indent=None):
83+
return json.dumps(message, indent=indent)
84+
85+
def format(self, record, serialize=True):
86+
# Create message dict
87+
message = {
88+
'timestamp': self.format_timestamp(record.created),
89+
'message': record.getMessage(),
90+
'host': self.host,
91+
'path': record.pathname,
92+
'tags': self.tags[:],
93+
'level': record.levelname,
94+
'logger': record.name,
95+
}
96+
97+
# Add extra fields
98+
message.update(self.get_extra_fields(record))
99+
100+
# Add extra tags
101+
if self.extra_tags:
102+
message['tags'].extend(self.extra_tags)
103+
104+
# If exception, add debug info
105+
if record.exc_info or record.exc_text:
106+
message.update(self.get_debug_fields(record))
107+
108+
if serialize:
109+
return self.serialize(message, indent=self.indent)
110+
return message
111+
112+
113+
class LogstashFormatterV0(JSONFormatter):
114+
version = 0
115+
116+
def format(self, record):
117+
# Create message dict
118+
message = {
119+
'@timestamp': self.format_timestamp(record.created),
120+
'@message': record.getMessage(),
121+
'@source': self.format_source(self.message_type, self.host,
122+
record.pathname),
123+
'@source_host': self.host,
124+
'@source_path': record.pathname,
125+
'@tags': self.tags[:],
126+
'@type': self.message_type,
127+
'@fields': {
128+
'levelname': record.levelname,
129+
'logger': record.name,
130+
},
131+
}
132+
133+
# Add extra fields
134+
message['@fields'].update(self.get_extra_fields(record))
135+
136+
# Add extra tags
137+
if self.extra_tags:
138+
message['@tags'].extend(self.extra_tags)
139+
140+
# If exception, add debug info
141+
if record.exc_info or record.exc_text:
142+
message['@fields'].update(self.get_debug_fields(record))
143+
144+
return self.serialize(message, indent=self.indent)
145+
146+
147+
class LogstashFormatterV1(JSONFormatter):
148+
def format(self, record):
149+
# Create message dict
150+
message = {
151+
'@timestamp': self.format_timestamp(record.created),
152+
'@version': 1,
153+
'message': record.getMessage(),
154+
'host': self.host,
155+
'path': record.pathname,
156+
'tags': self.tags[:],
157+
'type': self.message_type,
158+
159+
# Extra Fields
160+
'levelname': record.levelname,
161+
'logger': record.name,
162+
}
163+
164+
# Add extra fields
165+
message.update(self.get_extra_fields(record))
166+
167+
# Add extra tags
168+
if self.extra_tags:
169+
message['tags'].extend(self.extra_tags)
170+
171+
# If exception, add debug info
172+
if record.exc_info or record.exc_text:
173+
message.update(self.get_debug_fields(record))
174+
175+
return self.serialize(message, indent=self.indent)

setup.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from setuptools import setup
2+
3+
setup(
4+
name='json-logging-py',
5+
version='0.1',
6+
description='',
7+
keywords=['json', 'logging', 'logstash'],
8+
author='Sebastien Estienne',
9+
author_email='[email protected]',
10+
url='https://github.com/sebest/json-logging-py',
11+
license='MIT',
12+
classifiers=[
13+
'Development Status :: 4 - Beta',
14+
'Intended Audience :: Developers',
15+
'License :: OSI Approved :: MIT License',
16+
'Natural Language :: English',
17+
'Operating System :: Unix',
18+
'Programming Language :: Python :: 2.7',
19+
'Programming Language :: Python :: 3',
20+
'Programming Language :: Python :: 3.4',
21+
'Operating System :: OS Independent',
22+
],
23+
py_modules=['jsonlogging'],
24+
setup_requires=['setuptools-markdown'],
25+
long_description_markdown_filename='README.md',
26+
)

0 commit comments

Comments
 (0)