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 )
0 commit comments