Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 45a6736

Browse files
committedApr 23, 2020
- fix #45
- fix #46 - refactoring - optimization log record
1 parent ea862d4 commit 45a6736

20 files changed

+157
-161
lines changed
 

‎CHANGELOG.md

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

7+
## 1.2.0 - 2020-04-10
8+
- fix #45
9+
- fix #46
10+
- refactoring
11+
- optimization log record
12+
713
## 1.1.0 - 2020-04-10
814
- Add the possibility to modify the formatter for request logs.
915

‎README.md

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ If you're using Cloud Foundry, it worth to check out the library [SAP/cf-python-
2121
1. Emit JSON logs ([format detail](#0-full-logging-format-references))
2222
2. Auto extract **correlation-id** for distributed tracing [\[1\]](#1-what-is-correlation-idrequest-id)
2323
3. Lightweight, no dependencies, minimal configuration needed (1 LoC to get it working)
24-
4. Fully compatible with Python **logging** module. Support both Python 2.7.x and 3.x
24+
4. Seamlessly integrate with Python native **logging** module. Support both Python 2.7.x and 3.x
2525
5. Support HTTP request instrumentation. Built in support for [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: .
26-
6. Support inject arbitrary extra properties to JSON log message.
26+
6. Highly customizable: support inject arbitrary extra properties to JSON log message, override logging formatter, etc.
2727

2828
# 2. Usage
2929
Install by running this command:
@@ -59,8 +59,7 @@ logger.info("test logging statement")
5959
import datetime, logging, sys, json_logging, flask
6060

6161
app = flask.Flask(__name__)
62-
json_logging.ENABLE_JSON_LOGGING = True
63-
json_logging.init_flask()
62+
json_logging.init_flask(enable_json=True)
6463
json_logging.init_request_instrument(app)
6564

6665
# init the logger as usual
@@ -71,6 +70,8 @@ logger.addHandler(logging.StreamHandler(sys.stdout))
7170
@app.route('/')
7271
def home():
7372
logger.info("test log statement")
73+
logger.info("test log statement with extra props", extra={'props': {"extra_property": 'extra_value'}})
74+
correlation_id = json_logging.get_correlation_id()
7475
return "Hello world : " + str(datetime.datetime.now())
7576

7677
if __name__ == "__main__":
@@ -81,9 +82,8 @@ if __name__ == "__main__":
8182
```python
8283
import logging, sys, json_logging, sanic
8384

84-
app = sanic.Sanic()
85-
json_logging.ENABLE_JSON_LOGGING = True
86-
json_logging.init_sanic()
85+
app = sanic.Sanic(name="sanic-web-app")
86+
json_logging.init_sanic(enable_json=True)
8787
json_logging.init_request_instrument(app)
8888

8989
# init the logger as usual
@@ -94,6 +94,12 @@ logger.addHandler(logging.StreamHandler(sys.stdout))
9494
@app.route("/")
9595
async def home(request):
9696
logger.info("test log statement")
97+
logger.info("test log statement with extra props", extra={'props': {"extra_property": 'extra_value'}})
98+
# this will be faster
99+
correlation_id = json_logging.get_correlation_id(request=request)
100+
# this will be slower, but will work in context you cant get a reference of request object
101+
correlation_id_without_request_obj = json_logging.get_correlation_id()
102+
97103
return sanic.response.text("hello world")
98104

99105
if __name__ == "__main__":
@@ -106,8 +112,7 @@ if __name__ == "__main__":
106112
import asyncio, logging, sys, json_logging, quart
107113

108114
app = quart.Quart(__name__)
109-
json_logging.ENABLE_JSON_LOGGING = True
110-
json_logging.init_quart()
115+
json_logging.init_quart(enable_json=True)
111116
json_logging.init_request_instrument(app)
112117

113118
# init the logger as usual
@@ -118,7 +123,8 @@ logger.addHandler(logging.StreamHandler(sys.stdout))
118123
@app.route('/')
119124
async def home():
120125
logger.info("test log statement")
121-
logger.info("test log statement", extra={'props': {"extra_property": 'extra_value'}})
126+
logger.info("test log statement with extra props", extra={'props': {"extra_property": 'extra_value'}})
127+
correlation_id = json_logging.get_correlation_id()
122128
return "Hello world"
123129

124130
if __name__ == "__main__":
@@ -129,11 +135,10 @@ if __name__ == "__main__":
129135
### Connexion
130136

131137
```python
132-
import datetime, logging, sys, json_logging, connexion
138+
import logging, sys, json_logging, connexion
133139

134140
app = connexion.FlaskApp(__name__)
135-
json_logging.ENABLE_JSON_LOGGING = True
136-
json_logging.init_connexion()
141+
json_logging.init_connexion(enable_json=True)
137142
json_logging.init_request_instrument(app)
138143

139144
app.add_api('api.yaml')
@@ -151,7 +156,10 @@ if __name__ == "__main__":
151156
Current request correlation-id can be retrieved and pass to downstream services call as follow:
152157

153158
```python
154-
correlation_id = json_logging.get_correlation_id()
159+
# this will be faster
160+
correlation_id = json_logging.get_correlation_id(request=request)
161+
# this will be slower, but will work in context where you couldn't get a reference of request object
162+
correlation_id_without_request_obj = json_logging.get_correlation_id()
155163
# use correlation id for downstream service calls here
156164
```
157165

@@ -178,7 +186,7 @@ logging library can be configured by setting the value in json_logging, all conf
178186

179187
Name | Description | Default value
180188
--- | --- | ---
181-
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']** , this have no effect on request logger | false
189+
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
182190
ENABLE_JSON_LOGGING_DEBUG | Whether to enable debug logging for this library for development purpose. | true
183191
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']
184192
EMPTY_VALUE | Default value when a logging record property is None | '-'
@@ -198,7 +206,7 @@ To add support for a new web framework, you need to extend following classes in
198206

199207
Class | Description | Mandatory
200208
--- | --- | ---
201-
RequestAdapter | Helper class help to extract logging-relevant information from HTTP request object | no
209+
RequestAdapter | Helper class help to extract logging-relevant information from HTTP request object | yes
202210
ResponseAdapter | Helper class help to extract logging-relevant information from HTTP response object | yes
203211
FrameworkConfigurator | Class to perform logging configuration for given framework as needed | no
204212
AppRequestInstrumentationConfigurator | Class to perform request instrumentation logging configuration | no
@@ -236,7 +244,7 @@ e.g.:
236244
"written_ts": 1514048137280721000,
237245
"component_id": "1d930c0xd-19-s3213",
238246
"component_name": "ny-component_name",
239-
"component_instance": 0,
247+
"component_instance_idx": 0,
240248
"logger": "test logger",
241249
"thread": "MainThread",
242250
"level": "INFO",
@@ -256,7 +264,7 @@ e.g.:
256264
"written_ts": 1514048137280721000,
257265
"component_id": "-",
258266
"component_name": "-",
259-
"component_instance": 0,
267+
"component_instance_idx": 0,
260268
"correlation_id": "1975a02e-e802-11e7-8971-28b2bd90b19a",
261269
"remote_user": "user_a",
262270
"request": "/index.html",
@@ -287,7 +295,7 @@ correlation_id | The timestamp in nano-second precision when this request metric
287295
type | Type of logging. "logs" or "request" | string |
288296
component_id | Uniquely identifies the software component that has processed the current request | string | 9e6f3ecf-def0-4baf-8fac-9339e61d5645
289297
component_name | A human-friendly name representing the software component | string | my-fancy-component
290-
component_instance | Instance's index of horizontally scaled service | string | 0
298+
component_instance_idx | Instance's index of horizontally scaled service | string | 0
291299

292300
- application logs
293301

@@ -352,6 +360,7 @@ password=
352360
build
353361
```bash
354362
python setup.py bdist_wheel --universal
363+
python setup.py sdist --universal
355364
```
356365

357366
pypitest
@@ -361,6 +370,6 @@ pip3 install json_logging --index-url https://test.pypi.org/simple/
361370
```
362371
pypi
363372
```
364-
twine upload --repository-url https://upload.pypi.org/legacy/ dist/*
365-
pip3 install json_logging
373+
pip3 install json_loggingtwine upload --repository-url https://upload.pypi.org/legacy/ dist/*
374+
366375
```

‎example/connexion-example/hello.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
import json_logging
55

66

7-
def post_greeting(name: str) -> str:
7+
def post_greeting(name):
88
return 'Hello {name}'.format(name=name)
99

1010

1111
def create():
1212
app = connexion.FlaskApp(__name__, port=9090, specification_dir='openapi/')
13-
json_logging.ENABLE_JSON_LOGGING = True
14-
json_logging.init_connexion()
13+
json_logging.init_connexion(enable_json=True)
1514
json_logging.init_request_instrument(app)
1615

1716
app.add_api('helloworld-api.yaml', arguments={'title': 'Hello World Example'})

‎example/connexion-example/openapi/helloworld-api.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ servers:
88

99
paths:
1010
/greeting/{name}:
11-
post:
11+
get:
1212
summary: Generate greeting
1313
description: Generates a greeting message.
1414
operationId: hello.post_greeting

‎example/custom_log_format.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ class CustomJSONLog(logging.Formatter):
1919
"""
2020
Customized logger
2121
"""
22-
python_log_prefix = 'python.'
22+
2323
def get_exc_fields(self, record):
2424
if record.exc_info:
2525
exc_info = self.format_exception(record.exc_info)
2626
else:
2727
exc_info = record.exc_text
28-
return {f'{self.python_log_prefix}exc_info': exc_info}
28+
return {'python.exc_info': exc_info}
2929

3030
@classmethod
3131
def format_exception(cls, exc_info):
@@ -38,13 +38,13 @@ def format(self, record):
3838
"caller": record.filename + '::' + record.funcName
3939
}
4040
json_log_object['data'] = {
41-
f'{self.python_log_prefix}logger_name': record.name,
42-
f'{self.python_log_prefix}module': record.module,
43-
f'{self.python_log_prefix}funcName': record.funcName,
44-
f'{self.python_log_prefix}filename': record.filename,
45-
f'{self.python_log_prefix}lineno': record.lineno,
46-
f'{self.python_log_prefix}thread': f'{record.threadName}[{record.thread}]',
47-
f'{self.python_log_prefix}pid': record.process
41+
"python.logger_name": record.name,
42+
"python.module": record.module,
43+
"python.funcName": record.funcName,
44+
"python.filename": record.filename,
45+
"python.lineno": record.lineno,
46+
"python.thread": record.threadName,
47+
"python.pid": record.process
4848
}
4949
if hasattr(record, 'props'):
5050
json_log_object['data'].update(record.props)
@@ -55,20 +55,17 @@ def format(self, record):
5555
return json.dumps(json_log_object)
5656

5757

58-
def logger_init():
59-
json_logging.__init(custom_formatter=CustomJSONLog)
60-
6158
# You would normally import logger_init and setup the logger in your main module - e.g.
6259
# main.py
6360

64-
logger_init()
61+
json_logging.init_non_web(custom_formatter=CustomJSONLog)
6562

6663
logger = logging.getLogger(__name__)
6764
logger.setLevel(logging.DEBUG)
6865
logger.addHandler(logging.StreamHandler(sys.stderr))
6966

7067
logger.info('Starting')
7168
try:
72-
1/0
73-
except: # noqa pylint: disable=bare-except
69+
1 / 0
70+
except: # noqa pylint: disable=bare-except
7471
logger.exception('You can\'t divide by zero')

‎example/flask_sample_app.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
import json_logging
77

88
app = flask.Flask(__name__)
9-
json_logging.ENABLE_JSON_LOGGING = True
10-
json_logging.init_flask()
9+
json_logging.init_flask(enable_json=True)
1110
json_logging.init_request_instrument(app)
1211

1312
# init the logger as usual
@@ -19,8 +18,10 @@
1918
@app.route('/')
2019
def home():
2120
logger.info("test log statement")
22-
logger.info("test log statement", extra={'props': {"extra_property": 'extra_value'}})
23-
return "Hello world"
21+
logger.info("test log statement with extra props", extra={'props': {"extra_property": 'extra_value'}})
22+
correlation_id = json_logging.get_correlation_id()
23+
return "hello world" \
24+
"\ncorrelation_id : " + correlation_id
2425

2526

2627
@app.route('/exception')

‎example/non_web.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
logger.addHandler(logging.StreamHandler(sys.stdout))
1313

1414
logger.info("test log statement")
15-
logger.info("test log statement", extra={'props': {"extra_property": 'extra_value'}})
15+
logger.info("test log statement with extra props", extra={'props': {"extra_property": 'extra_value'}})

‎example/quart_sample_app.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
import json_logging
88

99
app = quart.Quart(__name__)
10-
json_logging.ENABLE_JSON_LOGGING = True
11-
json_logging.init_quart()
10+
json_logging.init_quart(enable_json=True)
1211
json_logging.init_request_instrument(app)
1312

1413
# init the logger as usual
@@ -20,8 +19,10 @@
2019
@app.route('/')
2120
async def home():
2221
logger.info("test log statement")
23-
logger.info("test log statement", extra={'props': {"extra_property": 'extra_value'}})
24-
return "Hello world"
22+
logger.info("test log statement with extra props", extra={'props': {"extra_property": 'extra_value'}})
23+
correlation_id = json_logging.get_correlation_id()
24+
return "hello world" \
25+
"\ncorrelation_id : " + correlation_id
2526

2627

2728
if __name__ == "__main__":

‎example/requirements.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
flask==1.0
2-
sanic==0.7.0
3-
Quart==0.6.7
2+
connexion[swagger-ui]
3+
quart
4+
sanic

‎example/sanic_sample_app.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
#!/usr/bin/env python3
2+
13
import json_logging
24
import logging
35
# noinspection PyPackageRequirements
46
import sanic
57
import sys
68

7-
app = sanic.Sanic()
8-
json_logging.ENABLE_JSON_LOGGING = True
9-
json_logging.__init(framework_name='sanic')
9+
app = sanic.Sanic(name="sanic-web-app")
10+
json_logging.init_sanic(enable_json=True)
1011
json_logging.init_request_instrument(app)
1112

1213
# init the logger as usual
@@ -15,13 +16,19 @@
1516
logger.addHandler(logging.StreamHandler(sys.stdout))
1617

1718

18-
# noinspection PyUnusedLocal
1919
@app.route("/")
20-
async def test(request):
20+
def test(request):
2121
logger.info("test log statement")
22-
logger.info("test log statement", extra={'props': {"extra_property": 'extra_value'}})
23-
# noinspection PyUnresolvedReferences
24-
return sanic.response.text("hello world")
22+
logger.info("test log statement with extra props", extra={'props': {"extra_property": 'extra_value'}})
23+
# this will be faster
24+
correlation_id = json_logging.get_correlation_id(request=request)
25+
# this will be slower, but will work in context you cant get a reference of request object
26+
correlation_id_without_request_obj = json_logging.get_correlation_id()
27+
28+
return sanic.response.text(
29+
"hello world"
30+
"\ncorrelation_id : " + correlation_id +
31+
"\ncorrelation_id_without_request_obj: " + correlation_id_without_request_obj)
2532

2633

2734
if __name__ == "__main__":

‎json_logging/__init__.py

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# coding=utf-8
22
import json
33
import logging
4+
import sys
45
import uuid
56
from datetime import datetime
67
import traceback
@@ -30,14 +31,14 @@
3031
_request_util = None
3132

3233

33-
def get_correlation_id():
34+
def get_correlation_id(request=None):
3435
"""
3536
Get current request correlation-id. If one is not present, a new one might be generated
3637
depends on CREATE_CORRELATION_ID_IF_NOT_EXISTS setting value.
3738
3839
:return: correlation-id string
3940
"""
40-
return _request_util.get_correlation_id()
41+
return _request_util.get_correlation_id(request=request)
4142

4243

4344
def register_framework_support(name, app_configurator, app_request_instrumentation_configurator, request_adapter_class,
@@ -96,11 +97,11 @@ def config_root_logger():
9697
"logging.basicConfig() or logging.getLogger('root')")
9798

9899

99-
def init_non_web(**kwargs):
100-
__init(**kwargs)
100+
def init_non_web(*args, **kw):
101+
__init(*args, **kw)
101102

102103

103-
def __init(framework_name=None, custom_formatter=None):
104+
def __init(framework_name=None, custom_formatter=None, enable_json=False):
104105
"""
105106
Initialize JSON logging support, if no **framework_name** passed, logging will be initialized in non-web context.
106107
This is supposed to be called only one time.
@@ -112,6 +113,7 @@ def __init(framework_name=None, custom_formatter=None):
112113
"""
113114

114115
global _current_framework
116+
global ENABLE_JSON_LOGGING
115117
if _current_framework is not None:
116118
raise RuntimeError("Can not call init more than once")
117119

@@ -138,11 +140,12 @@ def __init(framework_name=None, custom_formatter=None):
138140
else:
139141
formatter = custom_formatter if custom_formatter else JSONLogFormatter
140142

141-
if not ENABLE_JSON_LOGGING:
143+
if not enable_json and not ENABLE_JSON_LOGGING:
142144
_logger.warning(
143145
"JSON format is not enable, normal log will be in plain text but request logging still in JSON format! "
144146
"To enable set ENABLE_JSON_LOGGING env var to either one of following values: ['true', '1', 'y', 'yes']")
145147
else:
148+
ENABLE_JSON_LOGGING = True
146149
logging._defaultFormatter = formatter()
147150

148151
# go to all the initialized logger and update it to use JSON formatter
@@ -163,7 +166,7 @@ def init_request_instrument(app=None, custom_formatter=None):
163166

164167
if _current_framework is None or _current_framework == '-':
165168
raise RuntimeError("please init the logging first, call init(framework_name) first")
166-
169+
167170
if custom_formatter:
168171
if not issubclass(custom_formatter, logging.Formatter):
169172
raise ValueError('custom_formatter is not subclass of logging.Formatter', custom_formatter)
@@ -172,8 +175,10 @@ def init_request_instrument(app=None, custom_formatter=None):
172175
configurator.config(app)
173176

174177
formatter = custom_formatter if custom_formatter else JSONRequestLogFormatter
175-
logger = configurator.request_logger
176-
util.update_formatter_for_loggers([logger], formatter)
178+
request_logger = configurator.request_logger
179+
request_logger.setLevel(logging.DEBUG)
180+
request_logger.addHandler(logging.StreamHandler(sys.stdout))
181+
util.update_formatter_for_loggers([request_logger], formatter)
177182

178183

179184
def get_request_logger():
@@ -197,7 +202,7 @@ class that keep HTTP request information for request instrumentation logging
197202
"""
198203

199204
def __init__(self, request, **kwargs):
200-
super(self.__class__, self).__init__(**kwargs)
205+
super(RequestInfo, self).__init__(**kwargs)
201206
utcnow = datetime.utcnow()
202207
self.request_start = utcnow
203208
self.request = request
@@ -223,20 +228,29 @@ class BaseJSONFormatter(logging.Formatter):
223228
"""
224229
Base class for JSON formatters
225230
"""
231+
base_object_common = {}
232+
233+
def __init__(self, *args, **kw):
234+
super(BaseJSONFormatter, self).__init__(*args, **kw)
235+
if COMPONENT_ID and COMPONENT_ID != EMPTY_VALUE:
236+
self.base_object_common["component_id"] = COMPONENT_ID
237+
if COMPONENT_NAME and COMPONENT_NAME != EMPTY_VALUE:
238+
self.base_object_common["component_name"] = COMPONENT_NAME
239+
if COMPONENT_INSTANCE_INDEX and COMPONENT_INSTANCE_INDEX != EMPTY_VALUE:
240+
self.base_object_common["component_instance_idx"] = COMPONENT_INSTANCE_INDEX
226241

227242
def format(self, record):
228243
log_object = self._format_log_object(record, request_util=_request_util)
229244
return JSON_SERIALIZER(log_object)
230245

231246
def _format_log_object(self, record, request_util):
232247
utcnow = datetime.utcnow()
233-
return {
248+
base_obj = {
234249
"written_at": util.iso_time_format(utcnow),
235250
"written_ts": util.epoch_nano_second(utcnow),
236-
"component_id": COMPONENT_ID,
237-
"component_name": COMPONENT_NAME,
238-
"component_instance": COMPONENT_INSTANCE_INDEX,
239251
}
252+
base_obj.update(self.base_object_common)
253+
return base_obj
240254

241255

242256
class JSONRequestLogFormatter(BaseJSONFormatter):
@@ -245,7 +259,7 @@ class JSONRequestLogFormatter(BaseJSONFormatter):
245259
"""
246260

247261
def _format_log_object(self, record, request_util):
248-
json_log_object = super()._format_log_object(record, request_util)
262+
json_log_object = super(JSONRequestLogFormatter, self)._format_log_object(record, request_util)
249263
request = record.request_info.request
250264
request_adapter = request_util.request_adapter
251265

@@ -271,7 +285,6 @@ def _format_log_object(self, record, request_util):
271285
"response_sent_at": record.request_info.response_sent_at
272286
})
273287
return json_log_object
274-
275288

276289

277290
def _sanitize_log_msg(record):
@@ -298,15 +311,15 @@ def format_exception(cls, exc_info):
298311
return ''.join(traceback.format_exception(*exc_info)) if exc_info else ''
299312

300313
def _format_log_object(self, record, request_util):
301-
json_log_object = super()._format_log_object(record, request_util)
314+
json_log_object = super(JSONLogFormatter, self)._format_log_object(record, request_util)
302315
json_log_object.update({
316+
"msg": _sanitize_log_msg(record),
303317
"type": "log",
304318
"logger": record.name,
305319
"thread": record.threadName,
306320
"level": record.levelname,
307321
"module": record.module,
308322
"line_no": record.lineno,
309-
"msg": _sanitize_log_msg(record),
310323
})
311324
if hasattr(record, 'props'):
312325
json_log_object.update(record.props)
@@ -323,9 +336,9 @@ class JSONLogWebFormatter(JSONLogFormatter):
323336
"""
324337

325338
def _format_log_object(self, record, request_util):
326-
json_log_object = super()._format_log_object(record, request_util)
339+
json_log_object = super(JSONLogWebFormatter, self)._format_log_object(record, request_util)
327340
json_log_object.update({
328-
"correlation_id": request_util.get_correlation_id(),
341+
"correlation_id": request_util.get_correlation_id(within_formatter=True),
329342
})
330343
return json_log_object
331344

@@ -339,8 +352,8 @@ def _format_log_object(self, record, request_util):
339352
flask_support.FlaskResponseAdapter)
340353

341354

342-
def init_flask(custom_formatter=None):
343-
__init(framework_name='flask', custom_formatter=custom_formatter)
355+
def init_flask(custom_formatter=None, enable_json=False):
356+
__init(framework_name='flask', custom_formatter=custom_formatter, enable_json=enable_json)
344357

345358

346359
# register sanic support
@@ -354,8 +367,8 @@ def init_flask(custom_formatter=None):
354367
SanicResponseAdapter)
355368

356369

357-
def init_sanic(custom_formatter=None):
358-
__init(framework_name='sanic', custom_formatter=custom_formatter)
370+
def init_sanic(custom_formatter=None, enable_json=False):
371+
__init(framework_name='sanic', custom_formatter=custom_formatter, enable_json=enable_json)
359372

360373

361374
# register quart support
@@ -367,8 +380,8 @@ def init_sanic(custom_formatter=None):
367380
quart_support.QuartResponseAdapter)
368381

369382

370-
def init_quart(custom_formatter=None):
371-
__init(framework_name='quart', custom_formatter=custom_formatter)
383+
def init_quart(custom_formatter=None, enable_json=False):
384+
__init(framework_name='quart', custom_formatter=custom_formatter, enable_json=enable_json)
372385

373386

374387
# register connexion support
@@ -380,5 +393,5 @@ def init_quart(custom_formatter=None):
380393
connexion_support.ConnexionResponseAdapter)
381394

382395

383-
def init_connexion(custom_formatter=None):
384-
__init(framework_name='connexion', custom_formatter=custom_formatter)
396+
def init_connexion(custom_formatter=None, enable_json=False):
397+
__init(framework_name='connexion', custom_formatter=custom_formatter, enable_json=enable_json)

‎json_logging/framework/connexion/__init__.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ def config(self, app):
4444

4545
# noinspection PyAttributeOutsideInit
4646
self.request_logger = logging.getLogger('connexion-request-logger')
47-
self.request_logger.setLevel(logging.DEBUG)
48-
self.request_logger.addHandler(logging.StreamHandler(sys.stdout))
47+
4948

5049
from flask import g
5150

@@ -81,9 +80,6 @@ def get_remote_user(self, request):
8180
else:
8281
return json_logging.EMPTY_VALUE
8382

84-
def is_in_request_context(self, request_):
85-
return _flask.has_request_context()
86-
8783
def get_http_header(self, request, header_name, default=None):
8884
if header_name in request.headers:
8985
return request.headers.get(header_name)

‎json_logging/framework/flask/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ def config(self, app):
4040

4141
# noinspection PyAttributeOutsideInit
4242
self.request_logger = logging.getLogger('flask-request-logger')
43-
self.request_logger.setLevel(logging.DEBUG)
44-
self.request_logger.addHandler(logging.StreamHandler(sys.stdout))
4543

4644
from flask import g
4745

@@ -77,9 +75,6 @@ def get_remote_user(self, request):
7775
else:
7876
return json_logging.EMPTY_VALUE
7977

80-
def is_in_request_context(self, request_):
81-
return _flask.has_request_context()
82-
8378
def get_http_header(self, request, header_name, default=None):
8479
if header_name in request.headers:
8580
return request.headers.get(header_name)

‎json_logging/framework/quart/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ def config(self, app):
4545

4646
# noinspection PyAttributeOutsideInit
4747
self.request_logger = logging.getLogger('quart.app')
48-
self.request_logger.setLevel(logging.DEBUG)
49-
self.request_logger.addHandler(logging.StreamHandler(sys.stdout))
5048

5149
from quart import g
5250

@@ -82,9 +80,6 @@ def get_remote_user(self, request):
8280
else:
8381
return json_logging.EMPTY_VALUE
8482

85-
def is_in_request_context(self, request_):
86-
return _quart.has_request_context()
87-
8883
def get_http_header(self, request, header_name, default=None):
8984
if header_name in request.headers:
9085
return request.headers.get(header_name)

‎json_logging/framework/sanic/__init__.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# coding=utf-8
22
import logging
3+
import logging.config
4+
35
import sys
46

57
import json_logging
@@ -26,14 +28,16 @@ def config(self):
2628

2729
# from sanic.config import LOGGING
2830
# noinspection PyPackageRequirements
29-
from sanic.log import LOGGING_CONFIG_DEFAULTS as LOGGING
31+
from sanic.log import LOGGING_CONFIG_DEFAULTS
32+
33+
LOGGING_CONFIG_DEFAULTS['disable_existing_loggers'] = False
34+
LOGGING_CONFIG_DEFAULTS['formatters']['generic']['class'] = "json_logging.JSONLogFormatter"
35+
del LOGGING_CONFIG_DEFAULTS['formatters']['generic']['format']
3036

31-
LOGGING['disable_existing_loggers'] = False
32-
LOGGING['formatters']['generic']['class'] = "json_logging.JSONLogFormatter"
33-
del LOGGING['formatters']['generic']['format']
37+
LOGGING_CONFIG_DEFAULTS['formatters']['access']['class'] = "json_logging.JSONLogFormatter"
38+
del LOGGING_CONFIG_DEFAULTS['formatters']['access']['format']
3439

35-
LOGGING['formatters']['access']['class'] = "json_logging.JSONLogFormatter"
36-
del LOGGING['formatters']['access']['format']
40+
# logging.config.dictConfig(LOGGING_CONFIG_DEFAULTS)
3741

3842

3943
class SanicAppRequestInstrumentationConfigurator(AppRequestInstrumentationConfigurator):
@@ -47,8 +51,8 @@ def config(self, app):
4751

4852
# noinspection PyAttributeOutsideInit
4953
self.request_logger = logging.getLogger('sanic-request')
50-
self.request_logger.setLevel(logging.DEBUG)
51-
self.request_logger.addHandler(logging.StreamHandler(sys.stdout))
54+
55+
logging.getLogger('sanic.access').disabled = True
5256

5357
@app.middleware('request')
5458
def before_request(request):
@@ -70,9 +74,6 @@ class SanicRequestAdapter(RequestAdapter):
7074
def get_current_request():
7175
raise NotImplementedError
7276

73-
def is_in_request_context(self, request):
74-
return request is not None
75-
7677
@staticmethod
7778
def support_global_request_object():
7879
return False

‎json_logging/framework_base.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def __new__(cls, *arg, **kwargs):
1212
@staticmethod
1313
def support_global_request_object():
1414
"""
15-
whether current framework supports global request object
15+
whether current framework supports global request object like Flask
1616
"""
1717
raise NotImplementedError
1818

@@ -26,7 +26,7 @@ def get_current_request():
2626
@staticmethod
2727
def get_request_class_type():
2828
"""
29-
class type of request object
29+
class type of request object, only need to specify in case the framework dont support global request object
3030
"""
3131
raise NotImplementedError
3232

@@ -48,13 +48,6 @@ def get_remote_user(self, request):
4848
"""
4949
raise NotImplementedError
5050

51-
def is_in_request_context(self, request):
52-
"""
53-
54-
:param request: request object
55-
"""
56-
raise NotImplementedError
57-
5851
def set_correlation_id(self, request, value):
5952
"""
6053
We can safely assume that request is valid request object.\n
@@ -166,7 +159,7 @@ def get_content_type(self, response):
166159

167160
class FrameworkConfigurator:
168161
"""
169-
Class to perform logging configuration for given framework as needed
162+
Class to perform logging configuration for given framework as needed, like disable built in request logging and other utils logging
170163
"""
171164

172165
def __new__(cls, *args, **kw):

‎json_logging/util.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from logging import Logger, StreamHandler
77

88
import json_logging
9+
import inspect
910

1011

1112
def is_env_var_toggle(var_name):
@@ -82,13 +83,11 @@ def iso_time_format(datetime_):
8283
int(datetime_.microsecond / 1000))
8384

8485

85-
_no_of_go_up_level = 11
8686
if hasattr(sys, '_getframe'):
87-
# noinspection PyProtectedMember,PyPep8
88-
currentframe = lambda: sys._getframe(_no_of_go_up_level)
87+
currentframe = lambda _no_of_go_up_level: sys._getframe(_no_of_go_up_level)
8988
else: # pragma: no cover
9089
# noinspection PyBroadException
91-
def currentframe():
90+
def currentframe(_no_of_go_up_level):
9291
"""Return the frame object for the caller's stack frame."""
9392
try:
9493
raise Exception
@@ -105,9 +104,12 @@ def __new__(cls, *args, **kw):
105104
if not hasattr(cls, '_instance'):
106105
request_adapter_class = kw['request_adapter_class']
107106
response_adapter_class = kw['response_adapter_class']
107+
108108
validate_subclass(request_adapter_class, json_logging.RequestAdapter)
109109
validate_subclass(response_adapter_class, json_logging.ResponseAdapter)
110+
110111
cls._instance = object.__new__(cls)
112+
111113
cls._instance.request_adapter_class = request_adapter_class
112114
cls._instance.response_adapter_class = response_adapter_class
113115
cls._instance.request_adapter = request_adapter_class()
@@ -117,7 +119,7 @@ def __new__(cls, *args, **kw):
117119

118120
return cls._instance
119121

120-
def get_correlation_id(self, request=None):
122+
def get_correlation_id(self, request=None,within_formatter=False):
121123
"""
122124
Gets the correlation id from the header of the request. \
123125
It tries to search from json_logging.CORRELATION_ID_HEADERS list, one by one.\n
@@ -133,9 +135,7 @@ def get_correlation_id(self, request=None):
133135
else:
134136
request = self.get_request_from_call_stack()
135137

136-
if not self.request_adapter.is_in_request_context(request):
137-
# _logger.debug("Not in request context, return %s", json_logging.EMPTY_VALUE,
138-
# extra={'correlation_id': '-'})
138+
if request is None:
139139
return json_logging.EMPTY_VALUE
140140

141141
# _logger.debug("Attempt to get correlation from request context", extra={'correlation_id': '-'})
@@ -151,7 +151,7 @@ def get_correlation_id(self, request=None):
151151

152152
return correlation_id if correlation_id else json_logging.EMPTY_VALUE
153153

154-
def get_request_from_call_stack(self):
154+
def get_request_from_call_stack(self, within_formatter=False):
155155
"""
156156
157157
:return: get request object from call stack
@@ -170,11 +170,15 @@ def get_request_from_call_stack(self):
170170
09 info [__init__.py:1279]
171171
10 logging statement
172172
"""
173+
module = inspect.getmodule(inspect.currentframe().f_back)
174+
175+
class_type = self.request_adapter_class.get_request_class_type()
176+
no_of_go_up_level = 11 if within_formatter else 1
177+
173178
# FIXME: find out the depth of logging call stack in Python 2.7
174-
f = currentframe()
179+
f = currentframe(no_of_go_up_level)
175180
while True:
176181
f_locals = f.f_locals
177-
class_type = self.request_adapter_class.get_request_class_type()
178182
if 'request' in f_locals:
179183
if isinstance(f_locals['request'], class_type):
180184
return f_locals['request']

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

‎test-requirements.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
flask==1.0
1+
flask==1.0
2+
connexion[swagger-ui]
3+
quart
4+
sanic

‎test.py

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.