11# -*- coding: utf-8 -*-
2-
2+ import copy
33import logging
44import warnings
55from logging .handlers import QueueHandler
66from logging .handlers import QueueListener
77from queue import Queue
8- from typing import Dict
8+ from typing import Dict , Callable , Any , Union
99from typing import Optional
1010from typing import Type
1111
1212from logging_loki import const
1313from logging_loki import emitter
1414
1515
16- class LokiQueueHandler (QueueHandler ):
16+ class TagMixin :
17+ """
18+ A mixin class to support callable tags.
19+
20+ This is to be inherited from as a first class, eg
21+ >>> class Handler(TagMixin, logging.Handler):
22+ >>> pass
23+ """
24+
25+ def __init__ (self , tags = None ):
26+ self .tags = tags or {}
27+
28+ def prepare (self , record ):
29+ # This is invoked in the same thread in which logging is invoked
30+ # assume the second class has a proper solution for prepare()
31+ try :
32+ record = self .__class__ .__bases__ [1 ].prepare (self , record )
33+ except AttributeError : # logging.Handler has no prepare
34+ pass
35+ record .tags = getattr (record , 'tags' , {})
36+ for key , value in (self .tags | record .tags ).items ():
37+ if callable (value ):
38+ value = value ()
39+ if value is None :
40+ continue
41+ record .__dict__ [key ] = value
42+ return record
43+
44+
45+ class LokiQueueHandler (TagMixin , QueueHandler ):
1746 """This handler automatically creates listener and `LokiHandler` to handle logs queue."""
1847
1948 def __init__ (self , queue : Queue , ** kwargs ):
2049 """Create new logger handler with the specified queue and kwargs for the `LokiHandler`."""
21- super ().__init__ (queue )
50+ QueueHandler .__init__ (self , queue )
51+ TagMixin .__init__ (self , kwargs .get ("tags" ))
2252 self .handler = LokiHandler (** kwargs ) # noqa: WPS110
2353 self .listener = QueueListener (self .queue , self .handler )
2454 self .listener .start ()
2555
2656
27- class LokiHandler (logging .Handler ):
57+ class LokiHandler (TagMixin , logging .Handler ):
2858 """
2959 Log handler that sends log records to Loki.
3060
@@ -39,7 +69,7 @@ class LokiHandler(logging.Handler):
3969 def __init__ (
4070 self ,
4171 url : str ,
42- tags : Optional [dict ] = None ,
72+ tags : Optional [Dict [ str , Union [ Any , Callable ]] ] = None ,
4373 auth : Optional [emitter .BasicAuth ] = None ,
4474 version : Optional [str ] = None ,
4575 ):
@@ -53,7 +83,8 @@ def __init__(
5383 version: Version of Loki emitter to use.
5484
5585 """
56- super ().__init__ ()
86+ logging .Handler .__init__ (self )
87+ TagMixin .__init__ (self , tags )
5788
5889 if version is None and const .emitter_ver == "0" :
5990 msg = (
@@ -64,10 +95,16 @@ def __init__(
6495 )
6596 warnings .warn (" " .join (msg ), DeprecationWarning )
6697
98+ my_tags = tags or {}
99+
67100 version = version or const .emitter_ver
68- if version not in self .emitters :
69- raise ValueError ("Unknown emitter version: {0}" .format (version ))
70- self .emitter = self .emitters [version ](url , tags , auth )
101+ if version == '0' and any (callable (value ) for value in my_tags .values ()):
102+ raise ValueError ('Loki V0 handler does not support callable tags!' )
103+
104+ try :
105+ self .emitter = self .emitters [version ](url , tags , auth )
106+ except KeyError as exc :
107+ raise ValueError ("Unknown emitter version: {0}" .format (version )) from exc
71108
72109 def handleError (self , record ): # noqa: N802
73110 """Close emitter and let default handler take actions on error."""
@@ -76,8 +113,9 @@ def handleError(self, record): # noqa: N802
76113
77114 def emit (self , record : logging .LogRecord ):
78115 """Send log record to Loki."""
116+ record = self .prepare (record )
79117 # noinspection PyBroadException
80118 try :
81- self .emitter (record , self . format ( record ) )
119+ self .emitter (record , record . lineno )
82120 except Exception :
83121 self .handleError (record )
0 commit comments