JSON Python Logging
Project description
json-logging
Python logging library to emit JSON log that can be easily indexed and searchable by logging infrastructure such as ELK.
1. Features
Emit JSON logs (see format detail in reference)
Support correlation-id [1]
Lightweight, no dependencies, minimal configuration needed (1 LoC to get it working)
Fully compatible with Python logging module. Support both Python 2.7.x and 3.x
Support HTTP request instrumentation. Built in support for Flask & Sanic. Extensible to support other web frameworks. PR welcome :smiley: .
Support inject arbitrary extra properties to JSON log message.
2. Usage
Install by running this command:
pip install json-logging
By default log will be emitted in normal format to ease the local development. To enable it on production set either json_logging.ENABLE_JSON_LOGGING or ENABLE_JSON_LOGGING environment variable to true.
2.1 Non-web application log
This mode don’t support correlation-id.
import json_logging, logging, sys
# log is initialized without a web framework name
json_logging.ENABLE_JSON_LOGGING = True
json_logging.init()
logger = logging.getLogger("test-logger")
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler(sys.stdout))
logger.info("test logging statement")
2.2 Web application log
Flask
import datetime, logging, sys, json_logging, flask
app = flask.Flask(__name__)
json_logging.ENABLE_JSON_LOGGING = True
json_logging.init(framework_name='flask')
json_logging.init_request_instrument(app)
# init the logger as usual
logger = logging.getLogger("test-logger")
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler(sys.stdout))
@app.route('/')
def home():
logger.info("test log statement")
return "Hello world : " + str(datetime.datetime.now())
if __name__ == "__main__":
app.run(host='0.0.0.0', port=int(5000), use_reloader=False)
Sanic
import logging, sys, json_logging, sanic
app = sanic.Sanic()
json_logging.ENABLE_JSON_LOGGING = True
json_logging.init(framework_name='sanic')
json_logging.init_request_instrument(app)
# init the logger as usual
logger = logging.getLogger("sanic-integration-test-app")
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler(sys.stdout))
@app.route("/")
async def home(request):
logger.info("test log statement")
return sanic.response.text("hello world")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
2.3 Get current correlation-id
Current request correlation-id can be retrieved and pass to downstream services call as follow:
correlation_id = json_logging.get_correlation_id()
# use correlation id for downstream service calls here
In request context, if one is not present, a new one might be generated depends on CREATE_CORRELATION_ID_IF_NOT_EXISTS setting value.
2.4 Log extra properties
Extra property can be added to logging statement as follow:
logger.info("test log statement", extra = {'props' : {'extra_property' : 'extra_value'}})
2.5 Root logger
If you want to use root logger as main logger to emit log. Made sure you call config_root_logger() after initialize root logger (by logging.basicConfig() or logging.getLogger(‘root’)) [2]
logging.basicConfig()
json_logging.config_root_logger()
3. Configuration
logging library can be configured by setting the value in json_logging
Name |
Description |
Default value |
---|---|---|
ENABLE_JSON_LOGGING |
Whether to enable JSON logging mode.Can be set as an environment variable, enable when set to to either one in following list (case-insensitive) [‘true’, ‘1’, ‘y’, ‘yes’] |
false |
ENABLE_JSON_LOGGING_DEBUG |
Whether to enable debug logging for this library for development purpose. |
true |
CORRELATION_ID_HEADERS |
List of HTTP headers that will be used to look for correlation-id value. HTTP headers will be searched one by one according to list order |
[‘X-Correlation-ID’, ‘X-Request-ID’] |
EMPTY_VALUE |
Default value when a logging record property is None |
‘-’ |
CORRELATION_ID_GENERATOR |
function to generate unique correlation-id |
uuid.uuid1 |
JSON_SERIALIZER |
function to encode object to JSON |
json.dumps |
COMPONENT_ID |
Uniquely identifies the software component that has processed the current request |
EMPTY_VALUE |
COMPONENT_NAME |
A human-friendly name representing the software component |
EMPTY_VALUE |
COMPONENT_INSTANCE_INDEX |
Instance’s index of horizontally scaled service |
0 |
CREATE_CORRELATION_ID_IF_NOT_EXISTS |
Whether to generate a new correlation-id in case one is not present |
True |
4. Python References
TODO: update Python API docs on Github page
5. Framework support plugin development
To add support for a new web framework, you need to extend following classes in **framework_base** and register support using **json_logging.register_framework_support** method:
Class |
Description |
Mandatory |
---|---|---|
RequestAdapter |
Helper class help to extract logging-relevant information from HTTP request object |
no |
ResponseAdapter |
Helper class help to extract logging-relevant information from HTTP response object |
yes |
FrameworkConfigurator |
Class to perform logging configuration for given framework as needed |
no |
AppRequestInstrumentationConfigurator |
Class to perform request instrumentation logging configuration |
no |
Take a look at **json_logging/base_framework.py**, **json_logging.flask** and **json_logging.sanic** packages for reference implementations.
6. FAQ & Troubleshooting
I configured everything, but no logs are printed out?
Forgot to add handlers to your logger?
Check whether logger is disabled.
Same log statement is printed out multiple times.
Check whether the same handler is added to both parent and child loggers [2]
If you using flask, by default option use_reloader is set to True which will start 2 instances of web application. change it to False to disable this behaviour [3]
Can not install Sanic on Windows?
you can install Sanic on windows by running these commands:
git clone --branch 0.7.0 https://github.com/channelcat/sanic.git set SANIC_NO_UVLOOP=true set SANIC_NO_UJSON=true pip3 install .
7. References
[0] Full logging format references
2 types of logging statement will be emitted by this library: - Application log: normal logging statement e.g.:
{ "type": "log", "written_at": "2017-12-23T16:55:37.280Z", "written_ts": 1514048137280721000, "component_id": "1d930c0xd-19-s3213", "component_name": "ny-component_name", "component_instance": 0, "logger": "test logger", "thread": "MainThread", "level": "INFO", "line_no": 22, "correlation_id": "1975a02e-e802-11e7-8971-28b2bd90b19a", "extra_property": "extra_value" }
Request log: request instrumentation logging statement which recorded request information such as response time, request size, etc.
{ "type": "request", "written_at": "2017-12-23T16:55:37.280Z", "written_ts": 1514048137280721000, "component_id": "-", "component_name": "-", "component_instance": 0, "correlation_id": "1975a02e-e802-11e7-8971-28b2bd90b19a", "remote_user": "user_a", "request": "/index.html", "referer": "-", "x_forwarded_for": "-", "protocol": "HTTP/1.1", "method": "GET", "remote_ip": "127.0.0.1", "request_size_b": 1234, "remote_host": "127.0.0.1", "remote_port": 50160, "request_received_at": "2017-12-23T16:55:37.280Z", "response_time_ms": 0, "response_status": 200, "response_size_b": "122", "response_content_type": "text/html; charset=utf-8", "response_sent_at": "2017-12-23T16:55:37.280Z" }
See following tables for detail format explanation:
Common field
Field |
Description |
Format |
Example |
---|---|---|---|
written_at |
The date when this log message was written. |
ISO 8601 YYYY-MM-DDTHH:M M:SS.milliZ |
2017-12-23T15:1 4:02.208Z |
written_ts |
The timestamp in nano-second precision when this request metric message was written. |
long number |
145682055381684 9408 |
correlation_id |
The timestamp in nano-second precision when this request metric message was written. |
string |
db2d002e-2702-4 1ec-66f5-c002a8 0a3d3f |
type |
Type of logging. “logs” or “request” |
string |
|
component_id |
Uniquely identifies the software component that has processed the current request |
string |
9e6f3ecf-def0-4 baf-8fac-9339e6 1d5645 |
component_name |
A human-friendly name representing the software component |
string |
my-component |
component_instance |
Instance’s index of horizontally scaled service |
string |
0 |
application logs
Field |
Description |
Format |
Example |
---|---|---|---|
msg |
The actual message string passed to the logger. |
string |
This is a log message |
level |
The log “level” indicating the severity of the log message. |
string |
INFO |
thread |
Identifies the execution thread in which this log message has been written. |
string |
http-nio-4655 |
logger |
The logger name that emits the log message. |
string |
requests-logger |
request logs:
Field |
Description |
Format |
Example |
---|---|---|---|
request |
request path that has been processed. |
string |
/get/api/v2 |
request_receive d_at |
The date when an incoming request was received by the producer. |
ISO 8601 YYYY-MM-DDTHH:M M:SS.milliZ The precision is in milliseconds. The timezone is UTC. |
2015-01-24 14:06:05.071Z |
response_sent_a t |
The date when the response to an incoming request was sent to the consumer. |
ditto |
2015-01-24 14:06:05.071Z |
response_time_m s |
How many milliseconds it took the producer to prepare the response. |
float |
43.476 |
protocol |
Which protocol was used to issue a request to a producer. In most cases, this will be HTTP (including a version specifier), but for outgoing requests reported by a producer, it may contain other values. E.g. a database call via JDBC may report, e.g. “JDBC/1.2” |
string |
HTTP/1.1 |
method |
The corresponding protocol method. |
string |
GET |
remote_ip |
IP address of the consumer (might be a proxy, might be the actual client) |
string |
192.168.0.1 |
remote_host |
host name of the consumer (might be a proxy, might be the actual client) |
string |
my.happy.host |
remote_port |
Which TCP port is used by the consumer to establish a connection to the remote producer. |
string |
1234 |
remote_user |
The username associated with the request |
string |
user_name |
request_size_b |
The size in bytes of the requesting entity or “body” (e.g., in case of POST requests). |
long |
1234 |
response_size_b |
The size in bytes of the response entity |
long |
1234 |
response_status |
The status code of the response. |
long |
200 |
response_conten t_type |
The MIME type associated with the entity of the response if available or specified |
long |
application/jso n |
referer |
For HTTP requests, identifies the address of the webpage (i.e. the URI or IRI) that linked to the resource being requested. |
string |
/index.html |
x_forwarded_for |
Comma-separated list of IP addresses, the left-most being the original client, followed by proxy server addresses that forwarded the client request. |
string |
192.0.2.60,10.1 2.9.23 |
[1] What is correlation-id/request id
https://stackoverflow.com/questions/25433258/what-is-the-x-request-id-http-header
[2] Python logging propagate
https://docs.python.org/3/library/logging.html#logging.Logger.propagate https://docs.python.org/2/library/logging.html#logging.Logger.propagate
[3] more on flask use_reloader
http://flask.pocoo.org/docs/0.12/errorhandling/#working-with-debuggers
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
File details
Details for the file json_logging-0.0.8-py2.py3-none-any.whl
.
File metadata
- Download URL: json_logging-0.0.8-py2.py3-none-any.whl
- Upload date:
- Size: 21.2 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.12.1 pkginfo/1.4.2 requests/2.18.4 setuptools/40.0.0 requests-toolbelt/0.8.0 tqdm/4.23.0 CPython/3.6.4
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | fc474fa173ff836816b0e07f551a2c7e8001249c22ba122b58581cbeeb573789 |
|
MD5 | ccf7d891bee3434c60ffff64ae443058 |
|
BLAKE2b-256 | b35361d4740475154bb169618aa5474a9b020912fc4be563d869ce4af1ef3d74 |