Skip to content

Commit ebb3cd4

Browse files
author
Bob Bui
committedApr 24, 2021
allow overriding of correlation id
1 parent dc80372 commit ebb3cd4

File tree

6 files changed

+122
-29
lines changed

6 files changed

+122
-29
lines changed
 

‎CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
55
This project adheres to [Semantic Versioning](http://semver.org/).
66
The format is based on [Keep a Changelog](http://keepachangelog.com/).
77

8+
## 1.4.0rc2 - 2021-04-23
9+
10+
- allow overriding of correlation id
11+
812
## 1.4.0rc1 - 2021-04-09
913

1014
- refactor

‎README.md

Lines changed: 110 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
# json-logging
2-
Python logging library to emit JSON log that can be easily indexed and searchable by logging infrastructure such as [ELK](https://www.elastic.co/elk-stack), [EFK](https://www.digitalocean.com/community/tutorials/how-to-set-up-an-elasticsearch-fluentd-and-kibana-efk-logging-stack-on-kubernetes), AWS Cloudwatch, GCP Stackdriver
32

4-
If you're using Cloud Foundry, it might worth to check out the library [SAP/cf-python-logging-support](https://github.com/SAP/cf-python-logging-support) which I'm also original author.
3+
Python logging library to emit JSON log that can be easily indexed and searchable by logging infrastructure such
4+
as [ELK](https://www.elastic.co/elk-stack)
5+
, [EFK](https://www.digitalocean.com/community/tutorials/how-to-set-up-an-elasticsearch-fluentd-and-kibana-efk-logging-stack-on-kubernetes)
6+
, AWS Cloudwatch, GCP Stackdriver
7+
8+
If you're using Cloud Foundry, it might worth to check out the
9+
library [SAP/cf-python-logging-support](https://github.com/SAP/cf-python-logging-support) which I'm also original
10+
author.
11+
512
# Content
13+
614
1. [Features](#1-features)
715
2. [Usage](#2-usage)
816
2.1 [Non-web application log](#21-non-web-application-log)
@@ -12,32 +20,42 @@ If you're using Cloud Foundry, it might worth to check out the library [SAP/cf-p
1220
2.5 [Root logger](#25-root-logger)
1321
2.6 [Custom log formatter](#26-custom-log-formatter)
1422
2.7 [Exclude certain URL from request instrumentation](#27-exclude-certain-url-from-request-instrumentation)
15-
3. [Configuration](#3-configuration)
23+
3. [Configuration](#3-configuration)
1624
4. [Python References](#4-python-references)
1725
5. [Framework support plugin development](#5-framework-support-plugin-development)
1826
6. [FAQ & Troubleshooting](#6-faq--troubleshooting)
1927
7. [References](#7-references)
2028

2129
# 1. Features
30+
2231
1. Emit JSON logs ([format detail](#0-full-logging-format-references))
2332
2. Lightweight, no dependencies, minimal configuration needed (1 LoC to get it working)
2433
3. Seamlessly integrate with Python native **logging** module. Support both Python 2.7.x and 3.x
2534
4. Auto extract **correlation-id** for distributed tracing [\[1\]](#1-what-is-correlation-idrequest-id)
26-
5. Support HTTP request instrumentation. Built in support for [FastAPI](https://fastapi.tiangolo.com/), [Flask](https://github.com/pallets/flask/), [Sanic](https://github.com/channelcat/sanic), [Quart](https://gitlab.com/pgjones/quart), [Connexion](https://github.com/zalando/connexion). Extensible to support other web frameworks. PR welcome :smiley: .
27-
6. Highly customizable: support inject arbitrary extra properties to JSON log message, override logging formatter, etc.
35+
5. Support HTTP request instrumentation. Built in support for [FastAPI](https://fastapi.tiangolo.com/)
36+
, [Flask](https://github.com/pallets/flask/), [Sanic](https://github.com/channelcat/sanic)
37+
, [Quart](https://gitlab.com/pgjones/quart), [Connexion](https://github.com/zalando/connexion). Extensible to support
38+
other web frameworks. PR welcome :smiley: .
39+
6. Highly customizable: support inject arbitrary extra properties to JSON log message, override logging formatter, etc.
2840
7. Production ready, has been used in production since 2017
2941

3042
# 2. Usage
43+
3144
Install by running this command:
32-
> pip install json-logging
45+
> pip install json-logging
3346
34-
By default log will be emitted in normal format to ease the local development. To enable it on production set enable_json in init_\<framework name\>(enable_json=True) method call (set **json_logging.ENABLE_JSON_LOGGING** or **ENABLE_JSON_LOGGING environment variable** to true is not recommended and will be deprecated in future versions).
47+
By default log will be emitted in normal format to ease the local development. To enable it on production set
48+
enable_json in init_\<framework name\>(enable_json=True) method call (set **json_logging.ENABLE_JSON_LOGGING** or **
49+
ENABLE_JSON_LOGGING environment variable** to true is not recommended and will be deprecated in future versions).
3550

36-
To configure, call **json_logging.init_< framework_name >()**. Once configured library will try to configure all loggers (existing and newly created) to emit log in JSON format.
51+
To configure, call **json_logging.init_< framework_name >()**. Once configured library will try to configure all
52+
loggers (existing and newly created) to emit log in JSON format.
3753
See following use cases for more detail.
3854

3955
## 2.1 Non-web application log
56+
4057
This mode don't support **correlation-id**.
58+
4159
```python
4260
import json_logging, logging, sys
4361

@@ -52,7 +70,9 @@ logger.info("test logging statement")
5270
```
5371

5472
## 2.2 Web application log
73+
5574
### FastAPI
75+
5676
```python
5777
import datetime, logging, sys, json_logging, fastapi, uvicorn
5878

@@ -65,18 +85,21 @@ logger = logging.getLogger("test-logger")
6585
logger.setLevel(logging.DEBUG)
6686
logger.addHandler(logging.StreamHandler(sys.stdout))
6787

88+
6889
@app.get('/')
6990
def home():
7091
logger.info("test log statement")
7192
logger.info("test log statement with extra props", extra={'props': {"extra_property": 'extra_value'}})
7293
correlation_id = json_logging.get_correlation_id()
7394
return "Hello world : " + str(datetime.datetime.now())
7495

96+
7597
if __name__ == "__main__":
7698
uvicorn.run(app, host='0.0.0.0', port=5000)
7799
```
78100

79101
### Flask
102+
80103
```python
81104
import datetime, logging, sys, json_logging, flask
82105

@@ -89,18 +112,21 @@ logger = logging.getLogger("test-logger")
89112
logger.setLevel(logging.DEBUG)
90113
logger.addHandler(logging.StreamHandler(sys.stdout))
91114

115+
92116
@app.route('/')
93117
def home():
94118
logger.info("test log statement")
95119
logger.info("test log statement with extra props", extra={'props': {"extra_property": 'extra_value'}})
96120
correlation_id = json_logging.get_correlation_id()
97121
return "Hello world : " + str(datetime.datetime.now())
98122

123+
99124
if __name__ == "__main__":
100125
app.run(host='0.0.0.0', port=int(5000), use_reloader=False)
101126
```
102127

103128
### Sanic
129+
104130
```python
105131
import logging, sys, json_logging, sanic
106132

@@ -113,6 +139,7 @@ logger = logging.getLogger("sanic-integration-test-app")
113139
logger.setLevel(logging.DEBUG)
114140
logger.addHandler(logging.StreamHandler(sys.stdout))
115141

142+
116143
@app.route("/")
117144
async def home(request):
118145
logger.info("test log statement")
@@ -124,6 +151,7 @@ async def home(request):
124151

125152
return sanic.response.text("hello world")
126153

154+
127155
if __name__ == "__main__":
128156
app.run(host="0.0.0.0", port=5000)
129157
```
@@ -142,13 +170,15 @@ logger = logging.getLogger("test logger")
142170
logger.setLevel(logging.DEBUG)
143171
logger.addHandler(logging.StreamHandler(sys.stdout))
144172

173+
145174
@app.route('/')
146175
async def home():
147176
logger.info("test log statement")
148177
logger.info("test log statement with extra props", extra={'props': {"extra_property": 'extra_value'}})
149178
correlation_id = json_logging.get_correlation_id()
150179
return "Hello world"
151180

181+
152182
if __name__ == "__main__":
153183
loop = asyncio.get_event_loop()
154184
app.run(host='0.0.0.0', port=int(5000), use_reloader=False, loop=loop)
@@ -175,7 +205,9 @@ if __name__ == "__main__":
175205
```
176206

177207
### Custom handler for request instrumentation
178-
you need to explicitly set JSONRequestLogFormatter as default formatter for any extra handler that is added to request_logger
208+
209+
you need to explicitly set JSONRequestLogFormatter as default formatter for any extra handler that is added to
210+
request_logger
179211

180212
```python
181213
request_logger = json_logging.get_request_logger()
@@ -185,49 +217,82 @@ request_logger.addHandler(handler)
185217
```
186218

187219
## 2.3 Get current correlation-id
220+
188221
Current request correlation-id can be retrieved and pass to downstream services call as follow:
189222

190223
```python
191224
# this will be faster
192225
correlation_id = json_logging.get_correlation_id(request=request)
193226
# this will be slower, but will work in context where you couldn't get a reference of request object
194227
correlation_id_without_request_obj = json_logging.get_correlation_id()
195-
# use correlation id for downstream service calls here
228+
# use correlation id for upstream service calls here
196229
```
197230

198-
In request context, if one is not present, a new one might be generated depends on CREATE_CORRELATION_ID_IF_NOT_EXISTS setting value.
231+
In request context, if one is not present, a new one might be generated depends on CREATE_CORRELATION_ID_IF_NOT_EXISTS
232+
setting value.
233+
234+
## 2.3 Get current correlation-id
235+
236+
Current request correlation-id can be retrieved and pass to downstream services call as follow:
237+
238+
```python
239+
# this will be faster
240+
correlation_id = json_logging.get_correlation_id(request=request)
241+
# this will be slower, but will work in context where you couldn't get a reference of request object
242+
correlation_id_without_request_obj = json_logging.get_correlation_id()
243+
# use correlation id for upstream service calls here
244+
```
199245

200246
## 2.4 Log extra properties
247+
201248
Extra property can be added to logging statement as follow:
249+
202250
```python
203-
logger.info("test log statement", extra = {'props' : {'extra_property' : 'extra_value'}})
251+
logger.info("test log statement", extra={'props': {'extra_property': 'extra_value'}})
252+
```
253+
254+
### Forced overriding correlation-id
255+
A custom correlation id can be passed to logging statement as follow:
256+
```
257+
logger.info("test log statement", extra={'props': {'correlation_id': 'custom_correlation_id'}})
204258
```
259+
205260
## 2.5 Root logger
206-
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()) [\[2\]](#2-python-logging-propagate)
261+
262+
If you want to use root logger as main logger to emit log. Made sure you call **config_root_logger()** after initialize
263+
root logger (by logging.basicConfig() or logging.getLogger()) [\[2\]](#2-python-logging-propagate)
264+
207265
```python
208266
logging.basicConfig()
209-
json_logging.init_<framework name >()
267+
json_logging.init_ < framework
268+
name > ()
210269
json_logging.config_root_logger()
211270
```
212271

213272
## 2.6 Custom log formatter
214-
Customer JSON log formatter can be passed to init method. see examples for more detail: [non web](https://github.com/thangbn/json-logging-python/blob/master/example/custom_log_format.py),
215-
[web](https://github.com/thangbn/json-logging-python/blob/master/example/custom_log_format_request.py)
216273

274+
Customer JSON log formatter can be passed to init method. see examples for more
275+
detail: [non web](https://github.com/thangbn/json-logging-python/blob/master/example/custom_log_format.py),
276+
[web](https://github.com/thangbn/json-logging-python/blob/master/example/custom_log_format_request.py)
217277

218278
## 2.7 Exclude certain URl from request instrumentation
219-
Certain URL can be excluded from request instrumentation by specifying a list of regex into **init_request_instrument** method like below:
279+
280+
Certain URL can be excluded from request instrumentation by specifying a list of regex into **init_request_instrument**
281+
method like below:
220282

221283
```python
222284
json_logging.init_request_instrument(app, exclude_url_patterns=[r'/exclude_from_request_instrumentation'])
223285
```
224286

225287
# 3. Configuration
226-
logging library can be configured by setting the value in json_logging, all configuration must be placed before json_logging.init method call
288+
289+
logging library can be configured by setting the value in json_logging, all configuration must be placed before
290+
json_logging.init method call
227291

228292
Name | Description | Default value
229293
--- | --- | ---
230-
ENABLE_JSON_LOGGING | **DEPRECATED** 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']** , this have no effect on request logger | false
294+
ENABLE_JSON_LOGGING | **
295+
DEPRECATED** 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']** , this have no effect on request logger | false
231296
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']
232297
EMPTY_VALUE | Default value when a logging record property is None | '-'
233298
CORRELATION_ID_GENERATOR | function to generate unique correlation-id | uuid.uuid1
@@ -242,7 +307,11 @@ CREATE_CORRELATION_ID_IF_NOT_EXISTS | Whether to generate a new correlation-id
242307
TODO: update Python API docs on Github page
243308

244309
# 5. Framework support plugin development
245-
To add support for a new web framework, you need to extend following classes in [**framework_base**](/blob/master/json_logging/framework_base.py) and register support using [**json_logging.register_framework_support**](https://github.com/thangbn/json-logging-python/blob/master/json_logging/__init__.py#L38) method:
310+
311+
To add support for a new web framework, you need to extend following classes in [**
312+
framework_base**](/blob/master/json_logging/framework_base.py) and register support using [**
313+
json_logging.register_framework_support**](https://github.com/thangbn/json-logging-python/blob/master/json_logging/__init__.py#L38)
314+
method:
246315

247316
Class | Description | Mandatory
248317
--- | --- | ---
@@ -251,9 +320,12 @@ ResponseAdapter | Helper class help to extract logging-relevant information from
251320
FrameworkConfigurator | Class to perform logging configuration for given framework as needed | no
252321
AppRequestInstrumentationConfigurator | Class to perform request instrumentation logging configuration | no
253322

254-
Take a look at [**json_logging/base_framework.py**](json_logging/framework_base.py), [**json_logging.flask**](json_logging/framework/flask) and [**json_logging.sanic**](json_logging/framework/sanic) packages for reference implementations.
323+
Take a look at [**json_logging/base_framework.py**](json_logging/framework_base.py), [**
324+
json_logging.flask**](json_logging/framework/flask) and [**json_logging.sanic**](json_logging/framework/sanic) packages
325+
for reference implementations.
255326

256327
# 6. FAQ & Troubleshooting
328+
257329
1. I configured everything, but no logs are printed out?
258330

259331
- Forgot to add handlers to your logger?
@@ -262,13 +334,17 @@ Take a look at [**json_logging/base_framework.py**](json_logging/framework_base.
262334
2. Same log statement is printed out multiple times.
263335

264336
- Check whether the same handler is added to both parent and child loggers [2]
265-
- 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\]](#3-more-on-flask-use-reloader)
337+
- If you using flask, by default option **use_reloader** is set to **True** which will start 2 instances of web
338+
application. change it to False to disable this behaviour [\[3\]](#3-more-on-flask-use-reloader)
266339

267340
# 7. References
341+
268342
## [0] Full logging format references
343+
269344
2 types of logging statement will be emitted by this library:
270-
- Application log: normal logging statement
271-
e.g.:
345+
346+
- Application log: normal logging statement e.g.:
347+
272348
```
273349
{
274350
"type": "log",
@@ -288,7 +364,10 @@ e.g.:
288364
"msg": "This is a message"
289365
}
290366
```
291-
- Request log: request instrumentation logging statement which recorded request information such as response time, request size, etc.
367+
368+
- Request log: request instrumentation logging statement which recorded request information such as response time,
369+
request size, etc.
370+
292371
```
293372
{
294373
"type": "request",
@@ -316,7 +395,9 @@ e.g.:
316395
"response_sent_at": "2017-12-23T16:55:37.280Z"
317396
}
318397
```
398+
319399
See following tables for detail format explanation:
400+
320401
- Common field
321402

322403
Field | Description | Format | Example
@@ -362,10 +443,14 @@ referer | For HTTP requests, identifies the address of the webpage (i.e. the URI
362443
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.12.9.23
363444

364445
## [1] What is correlation-id/request id
446+
365447
https://stackoverflow.com/questions/25433258/what-is-the-x-request-id-http-header
448+
366449
## [2] Python logging propagate
450+
367451
https://docs.python.org/3/library/logging.html#logging.Logger.propagate
368452
https://docs.python.org/2/library/logging.html#logging.Logger.propagate
369453

370454
## [3] more on flask use_reloader
455+
371456
http://flask.pocoo.org/docs/0.12/errorhandling/#working-with-debuggers

‎example/flask_sample_app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
def home():
2020
logger.info("test log statement")
2121
logger.info("test log statement with extra props", extra={'props': {"extra_property": 'extra_value'}})
22+
logger.info("test log statement with custom correlation id", extra={'props': {'correlation_id': 'custom_correlation_id'}})
23+
2224
correlation_id = json_logging.get_correlation_id()
2325
return "hello world" \
2426
"\ncorrelation_id : " + correlation_id

‎example/non_web.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@
1212

1313
logger.info("test log statement")
1414
logger.info("test log statement with extra props", extra={'props': {"extra_property": 'extra_value'}})
15+
logger.info("test log statement with custom correlation id", extra={'props': {'correlation_id': 'custom_correlation_id'}})

‎json_logging/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,10 @@ class JSONLogWebFormatter(JSONLogFormatter):
358358

359359
def _format_log_object(self, record, request_util):
360360
json_log_object = super(JSONLogWebFormatter, self)._format_log_object(record, request_util)
361-
json_log_object.update({
362-
"correlation_id": request_util.get_correlation_id(within_formatter=True),
363-
})
361+
if "correlation_id" not in json_log_object:
362+
json_log_object.update({
363+
"correlation_id": request_util.get_correlation_id(within_formatter=True),
364+
})
364365
return json_log_object
365366

366367

‎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="json-logging",
15-
version='1.4.0-rc1',
15+
version='1.4.0-rc2',
1616
packages=find_packages(exclude=['contrib', 'docs', 'tests*', 'example', 'dist', 'build']),
1717
license='Apache License 2.0',
1818
description="JSON Python Logging",

0 commit comments

Comments
 (0)