Skip to content

Commit 771e19b

Browse files
authored
Merge pull request #504 from hellohaptik/ML-3234/ner_maintenance_error_n_logs
ner maintenance error n logs
2 parents c7e4e1a + 4432b95 commit 771e19b

7 files changed

Lines changed: 304 additions & 30 deletions

File tree

chatbot_ner/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from __future__ import absolute_import
22

3-
import logging
3+
import structlog
44
import os
55

66
from elasticsearch import RequestsHttpConnection
77
from requests_aws4auth import AWS4Auth
88

9-
ner_logger = logging.getLogger('chatbot_ner')
9+
ner_logger = structlog.getLogger('chatbot_ner')
1010

1111
ENGINE = os.environ.get('ENGINE')
1212
# ES settings (Mandatory to use Text type entities)

chatbot_ner/settings.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313

1414
import os
1515
import sys
16+
import structlog
1617

1718
from chatbot_ner.setup_sentry import setup_sentry
19+
from chatbot_ner.setup_logs import setup_logs
1820

1921
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
2022
ENVIRONMENT = os.environ.get('ENVIRONMENT') or os.environ.get('HAPTIK_ENV')
@@ -53,6 +55,7 @@
5355
'django.contrib.auth.middleware.AuthenticationMiddleware',
5456
'django.contrib.messages.middleware.MessageMiddleware',
5557
'django.middleware.clickjacking.XFrameOptionsMiddleware',
58+
'django_structlog.middlewares.RequestMiddleware',
5659
]
5760

5861
ROOT_URLCONF = 'chatbot_ner.urls'
@@ -134,25 +137,37 @@
134137
'default': {
135138
'format': '%(asctime)s %(levelname)s %(message)s %(module)s:%(lineno)d'
136139
},
140+
'time_log': {
141+
'format': '%(levelname)s %(asctime)s %(module)s %(message)s'
142+
},
143+
"json_formatter": {
144+
"()": structlog.stdlib.ProcessorFormatter,
145+
"processor": structlog.processors.JSONRenderer(),
146+
}
137147
},
138148
'handlers': {
139149
'stdout': {
140150
'level': DJANGO_LOG_LEVEL,
141151
'class': 'logging.StreamHandler',
142152
'stream': sys.stdout,
143-
'formatter': 'default'
153+
'formatter': 'json_formatter'
144154
},
145155
'requests_file': {
146156
'level': 'WARNING',
147157
'class': 'logging.handlers.WatchedFileHandler',
148158
'filename': os.path.join(BASE_DIR, 'logs', 'requests.log'),
149-
'formatter': 'default'
159+
'formatter': 'json_formatter'
150160
},
151161
'chatbot_ner_file': {
152162
'level': DJANGO_LOG_LEVEL,
153163
'class': 'logging.handlers.WatchedFileHandler',
154164
'filename': os.path.join(BASE_DIR, 'logs', 'ner_log.log'),
155-
'formatter': 'default'
165+
'formatter': 'json_formatter'
166+
},
167+
'audit': {
168+
"class": "logging.handlers.WatchedFileHandler",
169+
"filename": os.path.join(BASE_DIR, 'logs', 'audit.log'),
170+
"formatter": "json_formatter",
156171
}
157172
},
158173
'loggers': {
@@ -180,13 +195,20 @@
180195
'handlers': ['stdout'],
181196
'level': 'WARNING',
182197
'propagate': False
198+
},
199+
"django_structlog": {
200+
"handlers": ["audit"],
201+
"level": "INFO",
183202
}
184203
}
185204
}
186205

187206
# setup sentry
188207
setup_sentry()
189208

209+
# setup logs
210+
setup_logs()
211+
190212
# APM
191213
_elastic_apm_enabled = (os.environ.get('ELASTIC_APM_ENABLED') or '').strip().lower()
192214
ELASTIC_APM_ENABLED = (_elastic_apm_enabled == 'true') and 'test' not in sys.argv

chatbot_ner/setup_logs.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"""
2+
This module contains functions and classes required for configuring structlog
3+
"""
4+
5+
import collections
6+
from typing import Dict, Any, NamedTuple, List
7+
8+
import logging
9+
import structlog
10+
import django.dispatch
11+
from django.http import HttpRequest
12+
from django_structlog.middlewares.request import get_request_header
13+
from django_structlog.signals import bind_extra_request_metadata
14+
15+
16+
class _LoggingKey(NamedTuple):
17+
namespace: str
18+
bind_key: str
19+
header_key: str
20+
meta_key: str
21+
22+
23+
class LoggingKeys(object):
24+
"""
25+
This class contains attributes that need to be logged,
26+
they will be logged in case they are present in request headers
27+
"""
28+
# Copied attributes from django-structlog
29+
REQUEST_ID = _LoggingKey(namespace='default', bind_key='request_id',
30+
header_key='x-request-id', meta_key='HTTP_X_REQUEST_ID')
31+
CORRELATION_ID = _LoggingKey(namespace='default', bind_key='correlation_id',
32+
header_key='x-correlation-id', meta_key='HTTP_X_CORRELATION_ID')
33+
# Things haptik wants bound to log lines for filtering
34+
MESSAGE_ID = _LoggingKey(namespace='haptik', bind_key='message_id',
35+
header_key='x-haptik-message-id', meta_key='HTTP_X_HAPTIK_MESSAGE_ID')
36+
37+
@classmethod
38+
def members(cls) -> List[_LoggingKey]:
39+
return [getattr(cls, attr) for attr in cls.__dict__ if isinstance(getattr(cls, attr), _LoggingKey)]
40+
41+
42+
ALLOWED_BIND_KEYS = set()
43+
NAMESPACE_TO_LOGGING_KEYS: Dict[str, List[_LoggingKey]] = collections.defaultdict(list)
44+
for loggingkey in LoggingKeys.members():
45+
ALLOWED_BIND_KEYS.add(loggingkey.bind_key)
46+
NAMESPACE_TO_LOGGING_KEYS[loggingkey.namespace].append(loggingkey)
47+
ALLOWED_BIND_KEYS = frozenset(ALLOWED_BIND_KEYS)
48+
49+
50+
def add_module_and_lineno(logger: logging.Logger, name: str, event_dict: Dict[str, Any]) -> Dict[str, Any]:
51+
"""
52+
Add module and line number to the event dict
53+
Args:
54+
logger (structlog.stdlib.BoundLogger): stuctlog logger
55+
name (str): logger method (info/debug/..)
56+
event_dict (dict): log event dict
57+
Returns:
58+
event_dict (dict)
59+
"""
60+
frame, module_str = structlog._frames._find_first_app_frame_and_name(additional_ignores=[__name__, 'logging'])
61+
event_dict['modline'] = f'{module_str}:{frame.f_lineno}'
62+
return event_dict
63+
64+
65+
def unbind_extras(logger):
66+
# TODO: disabled to avoid too much logging, discuss with team
67+
logger.try_unbind('ip', 'username', 'user_id', 'request_path', 'task_id', 'parent_task_id')
68+
69+
70+
def update_request_metadata(
71+
signal: django.dispatch.Signal,
72+
sender: Any,
73+
request: HttpRequest,
74+
logger: structlog.stdlib.BoundLogger,
75+
**kwargs
76+
) -> None:
77+
"""
78+
Receive signal bind_extra_request_metadata from django_structlog to bind haptik specific vars
79+
if they are available in the request headers
80+
81+
Args:
82+
signal: reference to signal itself
83+
sender: reference to sender of the signal
84+
request: django HttpRequest instance
85+
logger: structlog logger instance
86+
"""
87+
context = {}
88+
for loggingkey in NAMESPACE_TO_LOGGING_KEYS.get('haptik', []):
89+
request_header = get_request_header(request, loggingkey.header_key, loggingkey.meta_key)
90+
if request_header:
91+
context[loggingkey.bind_key] = request_header
92+
logger.bind(**context)
93+
unbind_extras(logger)
94+
95+
96+
def setup_logs():
97+
# configuring struct log
98+
structlog.configure(
99+
processors=[
100+
structlog.stdlib.filter_by_level,
101+
structlog.processors.TimeStamper(fmt="iso"),
102+
structlog.stdlib.add_log_level,
103+
structlog.stdlib.PositionalArgumentsFormatter(),
104+
structlog.processors.StackInfoRenderer(),
105+
structlog.processors.format_exc_info,
106+
structlog.processors.UnicodeDecoder(),
107+
add_module_and_lineno,
108+
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
109+
],
110+
context_class=structlog.threadlocal.wrap_dict(dict),
111+
logger_factory=structlog.stdlib.LoggerFactory(),
112+
wrapper_class=structlog.stdlib.BoundLogger,
113+
cache_logger_on_first_use=True,
114+
)
115+
bind_extra_request_metadata.connect(update_request_metadata)

datastore/elastic_search/query.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from language_utilities.constant import ENGLISH_LANG
1818
from lib.nlp.const import TOKENIZER
1919

20+
from elasticsearch import exceptions as es_exceptions
21+
2022
# Local imports
2123

2224
log_prefix = 'datastore.elastic_search.query'
@@ -312,6 +314,12 @@ def full_text_query(connection, index_name, doc_type, entity_name, sentences, fu
312314
try:
313315
response = _run_es_search(connection, msearch=True, **kwargs)
314316
results = _parse_es_search_results(response.get("responses"))
317+
except es_exceptions.NotFoundError as e:
318+
raise DataStoreRequestException(f'NotFoundError in datastore query on index: {index_name}',
319+
engine='elasticsearch', request=json.dumps(data),
320+
response=json.dumps(response)) from e
321+
except es_exceptions.ConnectionError as e:
322+
raise e
315323
except Exception as e:
316324
raise DataStoreRequestException(f'Error in datastore query on index: {index_name}', engine='elasticsearch',
317325
request=json.dumps(data), response=json.dumps(response)) from e

0 commit comments

Comments
 (0)