1+ """Generate and store thread local logging context including unique
2+ trace id for request, request source etc. to be logged.
3+
4+ Trace id generator can use nanoid or uuid.uuid4 stdlib function.
5+ Nanoid is preferred if nanoid is installed using pip as nanoid is
6+ faster and generates a shorter id. If nanoid is installed in the
7+ tracker's lib subdirectory, it must be enabled using the tracker's
8+ interfaces.py by adding::
9+
10+ # if nanoid is installed in tracker's lib directory or
11+ # if you want to change the length of the nanoid from 12
12+ # to 14 chars use:
13+ from functools import partial
14+ from nanoid import generate
15+ import roundup.logcontext
16+ # change 14 to 12 to get the default nanoid size.
17+ roundup.logcontext.idgen=partial(generate, size=14)
18+
19+ # to force use of shortened uuid when nanoid is
20+ # loaded by default
21+ import roundup.logcontext
22+ roundup.logcontext.idgen=roundup.logcontext.short_uuid
23+
24+ """
125import contextvars
226import functools
327import logging
428import os
529import uuid
630
31+
32+ def short_uuid ():
33+ """Encode a UUID integer in a shorter form for display.
34+
35+ A uuid is long. Make a shorter version that takes less room
36+ in a log line and is easier to store.
37+ """
38+ alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
39+ result = ""
40+ alphabet_len = len (alphabet )
41+ uuid_int = uuid .uuid4 ().int
42+ while uuid_int :
43+ uuid_int , t = divmod (uuid_int , alphabet_len )
44+ result += alphabet [t ]
45+ return result or "0"
46+
47+
48+ try :
49+ from nanoid import generate
50+ # With size=12 and the normal alphabet, it take ~4 months
51+ # with 1000 nanoid's/sec to generate a collision with 1%
52+ # probability. That's 100 users sec continously. These id's
53+ # are used to link logging messages/traces that are all done
54+ # in a few seconds. Collisions ae unlikely to happen in the
55+ # same time period leading to confusion.
56+ #
57+ # nanoid is faster than shortened uuids.
58+ # 1,000,000 generate(size=12) timeit.timeit at 25.4 seconds
59+ # 1,000,000 generate(size=21) timeit.timeit at 33.7 seconds
60+
61+ #: Variable used for setting the id generator.
62+ idgen = functools .partial (generate , size = 12 )
63+ except ImportError :
64+ # 1,000,000 of short_uuid() timeit.timeit at 54.1 seconds
65+ idgen = short_uuid #: :meta hide-value:
66+
67+
768logger = logging .getLogger ("roundup.logcontext" )
869
970
@@ -46,6 +107,11 @@ def __repr__(self):
46107
47108# set up sentinel values that will print a suitable error value
48109# and the context vars they are associated with.
110+ _SENTINEL_PROCESSNAME = SimpleSentinel ("processName" , None )
111+ ctx_vars ['processName' ] = contextvars .ContextVar ("processName" ,
112+ default = _SENTINEL_PROCESSNAME )
113+
114+
49115_SENTINEL_ID = SimpleSentinel ("trace_id" , "not set" )
50116ctx_vars ['trace_id' ] = contextvars .ContextVar ("trace_id" , default = _SENTINEL_ID )
51117
@@ -55,23 +121,8 @@ def __repr__(self):
55121 default = _SENTINEL_REASON )
56122
57123
58- def shorten_int_uuid (uuid ):
59- """Encode a UUID integer in a shorter form for display.
60-
61- A uuid is long. Make a shorter version that takes less room
62- in a log line.
63- """
64-
65- alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
66- result = ""
67- while uuid :
68- uuid , t = divmod (uuid , len (alphabet ))
69- result += alphabet [t ]
70- return result or "0"
71-
72-
73124def gen_trace_id ():
74- """Decorator to generate a trace id (encoded uuid4) and add to contextvar
125+ """Decorator to generate a trace id (nanoid or encoded uuid4) as contextvar
75126
76127 The logging routine uses this to label every log line. All
77128 logs with the same trace_id should be generated from a
@@ -84,23 +135,30 @@ def gen_trace_id():
84135 used by a different invocation method. It will not set a
85136 trace_id if one is already assigned.
86137
87- A uuid4() is used as the uuid, but to shorten the log line,
88- the uuid4 integer is encoded into a 62 character ascii
89- alphabet (A-Za-z0-9) .
138+ If a uuid4() is used as the id, the uuid4 integer is encoded
139+ into a 62 character alphabet (A-Za-z0-9) to shorten
140+ the log line .
90141
91142 This decorator may produce duplicate (colliding) trace_id's
92143 when used with multiple processes on some platforms where
93144 uuid.uuid4().is_safe is unknown. Probability of a collision
94145 is unknown.
95146
147+ If nanoid is used to generate the id, it is 12 chars long and
148+ uses a 64 char ascii alphabet, the 62 above with '_' and '-'.
149+ The shorter nanoid has < 1% chance of collision in ~4 months
150+ when generating 1000 id's per second.
151+
152+ See the help text for the module to change how the id is
153+ generated.
96154 """
97155 def decorator (func ):
98156 @functools .wraps (func )
99157 def wrapper (* args , ** kwargs ):
100158 prev = None
101159 trace_id = ctx_vars ['trace_id' ]
102160 if trace_id .get () is _SENTINEL_ID :
103- prev = trace_id .set (shorten_int_uuid ( uuid . uuid4 (). int ))
161+ prev = trace_id .set (idgen ( ))
104162 try :
105163 r = func (* args , ** kwargs )
106164 finally :
@@ -111,6 +169,26 @@ def wrapper(*args, **kwargs):
111169 return decorator
112170
113171
172+ def set_processName (name ):
173+ """Decorator to set the processName used in the LogRecord
174+ """
175+ def decorator (func ):
176+ @functools .wraps (func )
177+ def wrapper (* args , ** kwargs ):
178+ prev = None
179+ processName = ctx_vars ['processName' ]
180+ if processName .get () is _SENTINEL_PROCESSNAME :
181+ prev = processName .set (name )
182+ try :
183+ r = func (* args , ** kwargs )
184+ finally :
185+ if prev :
186+ processName .reset (prev )
187+ return r
188+ return wrapper
189+ return decorator
190+
191+
114192def store_trace_reason (location = None ):
115193 """Decorator finds and stores a reason trace was started in contextvar.
116194
@@ -195,7 +273,7 @@ def get_context_info():
195273
196274#Is returning a dict for this info more pythonic?
197275def get_context_dict ():
198- """Return dict of context var tuples [ "var_name": "var_value", ...}"""
276+ """Return dict of context var tuples { "var_name": "var_value", ...}"""
199277 return {name : ctx .get () for name , ctx in ctx_vars .items ()}
200278
201279# Dummy no=op implementation of this module:
0 commit comments