44from abc import ABC , abstractmethod
55from enum import Enum
66from logging import LogRecord
7- from typing import Any , Dict , Optional , Sequence , Union
7+ from typing import Any , Dict , List , Optional , Sequence , Union
88
99from attrs import define
1010from slack_sdk .models .attachments import Attachment
11- from slack_sdk .models .blocks import Block , DividerBlock , HeaderBlock , SectionBlock
11+ from slack_sdk .models .blocks import Block , ContextBlock , DividerBlock , HeaderBlock , SectionBlock
1212from slack_sdk .models .blocks .basic_components import MarkdownTextObject , PlainTextObject
1313from slack_sdk .webhook .async_client import AsyncWebhookClient
1414from slack_sdk .webhook .webhook_response import WebhookResponse
3333class Configuration :
3434 service : Optional [str ] = None
3535 environment : Optional [str ] = None
36+ context : List [str ] = []
3637 emojis : Dict [int , str ] = DEFAULT_EMOJIS
3738 extra_fields : Dict [str , str ] = {}
3839
@@ -43,6 +44,51 @@ class MessageDesign(ABC):
4344 def format_blocks (self , record : LogRecord ) -> Sequence [Optional [Block ]]:
4445 pass
4546
47+ def get_env (self , config : Configuration , record : LogRecord ) -> Optional [str ]:
48+ dynamic_env : Optional [str ] = getattr (record , "environment" , None )
49+ if dynamic_env is not None :
50+ return dynamic_env
51+ if config .environment is not None :
52+ return config .environment
53+ return None
54+
55+ def get_service (self , config : Configuration , record : LogRecord ) -> Optional [str ]:
56+ dynamic_service : Optional [str ] = getattr (record , "service" , None )
57+ if dynamic_service is not None :
58+ return dynamic_service
59+ if config .service is not None :
60+ return config .service
61+ return None
62+
63+ def construct_header (
64+ self , record : LogRecord , config : Configuration , icon : Optional [str ], level : str
65+ ) -> HeaderBlock :
66+ service : Optional [str ] = self .get_service (config = config , record = record )
67+ header_msg : str
68+ if icon is not None :
69+ header_msg = f"{ icon } "
70+ header_msg += level
71+ if config .service is not None :
72+ header_msg += f" | { service } "
73+ else :
74+ header_msg += f" | { record .name } "
75+
76+ return HeaderBlock (text = PlainTextObject (text = header_msg ))
77+
78+ def construct_context (
79+ self , config : Configuration , env : Optional [str ], service : Optional [str ]
80+ ) -> Optional [ContextBlock ]:
81+ if config .context != []:
82+ context_msg = ", " .join (config .context )
83+ return ContextBlock (elements = [MarkdownTextObject (text = context_msg )])
84+ elif env is not None and service is not None :
85+ return ContextBlock (elements = [MarkdownTextObject (text = f":point_right: { env } , { service } " )])
86+ elif env is None :
87+ return ContextBlock (elements = [MarkdownTextObject (text = f":point_right: { env } " )])
88+ elif service is None :
89+ return ContextBlock (elements = [MarkdownTextObject (text = f":point_right: { service } " )])
90+ return None
91+
4692 def format (self , record : LogRecord ) -> str :
4793 maybe_blocks : Sequence [Optional [Block ]] = self .format_blocks (record = record )
4894 blocks : Sequence [Block ] = [b for b in maybe_blocks if b is not None ]
@@ -66,11 +112,7 @@ def format_blocks(self, record: LogRecord) -> Sequence[Optional[Block]]:
66112 message = record .getMessage ()
67113 icon = self .config .emojis .get (record .levelno )
68114
69- header : HeaderBlock
70- if icon is not None :
71- header = HeaderBlock (text = PlainTextObject (text = f"{ icon } { level } | { self .config .service } " ))
72- else :
73- header = HeaderBlock (text = PlainTextObject (text = f"{ level } | { self .config .service } " ))
115+ header : HeaderBlock = self .construct_header (record = record , config = self .config , icon = icon , level = level )
74116
75117 body = SectionBlock (text = MarkdownTextObject (text = message ))
76118 default_blocks : Sequence [Block ] = [
@@ -90,31 +132,28 @@ def format_blocks(self, record: LogRecord) -> Sequence[Optional[Block]]:
90132 message = record .getMessage ()
91133 icon = self .config .emojis .get (record .levelno )
92134
93- dynamic_extra_fields = getattr (record , "extra_fields" , {})
94- all_extra_fields = {** self .config .extra_fields , ** dynamic_extra_fields }
95-
96- header : HeaderBlock
97- if icon is not None :
98- header = HeaderBlock (text = PlainTextObject (text = f"{ icon } { level } | { self .config .service } " ))
99- else :
100- header = HeaderBlock (text = PlainTextObject (text = f"{ level } | { self .config .service } " ))
135+ env : Optional [str ] = self .get_env (config = self .config , record = record )
136+ service : Optional [str ] = self .get_service (config = self .config , record = record )
101137
138+ header : HeaderBlock = self .construct_header (record = record , config = self .config , icon = icon , level = level )
139+ context : Optional [ContextBlock ] = self .construct_context (config = self .config , env = env , service = service )
102140 body = SectionBlock (text = MarkdownTextObject (text = message ))
103141
104142 error : Optional [SectionBlock ] = None
105143 if record .exc_info is not None :
106144 error = SectionBlock (text = MarkdownTextObject (text = f"```{ record .exc_text } ```" ))
107145
108- fields = SectionBlock (
109- fields = [
110- MarkdownTextObject ( text = f"*Environment* \n { self . config . environment } " ),
111- MarkdownTextObject ( text = f"*Service* \n { self . config . service } " ),
112- ]
113- + [MarkdownTextObject (text = f"*{ key } *\n { value } " ) for key , value in all_extra_fields .items ()]
114- )
146+ dynamic_extra_fields = getattr ( record , "extra_fields" , {})
147+ all_extra_fields = { ** self . config . extra_fields , ** dynamic_extra_fields }
148+ fields : Optional [ SectionBlock ] = None
149+ if all_extra_fields != {}:
150+ fields = SectionBlock (
151+ fields = [MarkdownTextObject (text = f"*{ key } *\n { value } " ) for key , value in all_extra_fields .items ()]
152+ )
115153
116154 maybe_blocks : Sequence [Optional [Block ]] = [
117155 header ,
156+ context ,
118157 DividerBlock (),
119158 body ,
120159 error ,
@@ -147,6 +186,7 @@ def default(cls, config: Configuration) -> "SlackFormatter":
147186 return cls (design = RichDesign (config ), config = config )
148187
149188 def format (self , record : LogRecord ) -> str :
189+ super ().format (record )
150190 return self .design .format (record )
151191
152192
@@ -266,7 +306,6 @@ def dummy(cls) -> "SlackHandler":
266306 return cls (client = DummyClient ())
267307
268308 async def send_text_via_webhook (self , text : str ) -> str :
269- log .debug (text )
270309 response = await self .client .send (text = text )
271310 assert response .status_code == 200
272311 assert response .body == "ok"
@@ -281,13 +320,10 @@ async def send_blocks_via_webhook(self, blocks: str) -> str:
281320
282321 def emit (self , record : LogRecord ) -> None :
283322 try :
323+ formatted_message = self .format (record )
284324 if isinstance (self .formatter , SlackFormatter ):
285- formatted_message = self .format (record )
286- log .debug (f"formatted_message: { formatted_message } " )
287325 asyncio .run (self .send_blocks_via_webhook (blocks = formatted_message ))
288326 else :
289- formatted_message = self .format (record )
290- log .debug (f"formatted_message: { formatted_message } " )
291327 asyncio .run (self .send_text_via_webhook (text = formatted_message ))
292328
293329 except Exception :
0 commit comments