1111class Telegramlogs extends AbstractProcessingHandler
1212{
1313 protected string $ botToken ;
14+
1415 protected string $ chatId ;
16+
1517 protected ?string $ topicId ;
18+
1619 protected Client $ client ;
20+
1721 protected bool $ ignoreEmptyMessages ;
22+
1823 protected int $ timeout ;
24+
1925 protected bool $ splitLongMessages ;
26+
2027 protected int $ maxMessageLength ;
2128
2229 public function __construct (
2330 $ level = Logger::DEBUG ,
2431 bool $ bubble = true ,
2532 ?Client $ client = null ,
2633 bool $ ignoreEmptyMessages = true ,
27- int $ timeout = null ,
28- bool $ splitLongMessages = null ,
29- int $ maxMessageLength = null
34+ ? int $ timeout = null ,
35+ ? bool $ splitLongMessages = null ,
36+ ? int $ maxMessageLength = null
3037 ) {
3138 parent ::__construct ($ level , $ bubble );
3239
@@ -50,122 +57,50 @@ protected function write(LogRecord $record): void
5057 return ;
5158 }
5259
53- $ formattedMessage = $ this ->formatRecord ($ record );
54-
5560 try {
56- $ this ->sendMessage ($ formattedMessage );
57- } catch (GuzzleException $ e ) {
58- // Log the error to the default logger if Telegram fails
59- error_log ('Failed to send Telegram log: ' . $ e ->getMessage ());
60- }
61- }
62-
63- protected function formatRecord (LogRecord $ record ): string
64- {
65- $ includeContext = config ('telegramlogs.formatting.include_context ' , true );
66- $ includeStackTrace = config ('telegramlogs.formatting.include_stack_trace ' , true );
67- $ parseMode = config ('telegramlogs.formatting.parse_mode ' , 'MarkdownV2 ' );
68-
69- $ context = '' ;
70- if ($ includeContext && $ record ->context ) {
71- $ context = "\n\nContext: \n```json \n" .
72- json_encode ($ record ->context , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) .
73- "\n``` " ;
74- }
61+ // Convert LogRecord to array for JSON serialization
62+ $ recordArray = $ record ->toArray ();
7563
76- $ extra = '' ;
77- if ($ record ->extra ) {
78- $ extra = "\n\nExtra: \n```json \n" .
79- json_encode ($ record ->extra , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) .
80- "\n``` " ;
81- }
82-
83- // Add stack trace for error-level logs if enabled
84- $ stackTrace = '' ;
85- if ($ includeStackTrace &&
86- $ record ->level ->value >= \Monolog \Level::Error->value &&
87- isset ($ record ->context ['exception ' ])) {
88- $ stackTrace = "\n\nStack Trace: \n``` \n" .
89- $ record ->context ['exception ' ]->getTraceAsString () .
90- "\n``` " ;
91- }
92-
93- if ($ parseMode === 'MarkdownV2 ' ) {
94- return sprintf (
95- "*[%s] %s* \n\n`%s`%s%s%s " ,
96- $ record ->datetime ->format ('Y-m-d H:i:s ' ),
97- strtoupper ($ record ->level ->getName ()),
98- $ this ->escapeMarkdown ($ record ->message ),
99- $ context ,
100- $ extra ,
101- $ stackTrace
102- );
103- } elseif ($ parseMode === 'HTML ' ) {
104- return sprintf (
105- "<b>[%s] %s</b> \n\n<code>%s</code>%s%s%s " ,
106- $ record ->datetime ->format ('Y-m-d H:i:s ' ),
107- strtoupper ($ record ->level ->getName ()),
108- htmlspecialchars ($ record ->message ),
109- str_replace (['```json ' , '``` ' ], ['<pre> ' , '</pre> ' ], $ context ),
110- str_replace (['```json ' , '``` ' ], ['<pre> ' , '</pre> ' ], $ extra ),
111- str_replace (['``` ' , '``` ' ], ['<pre> ' , '</pre> ' ], $ stackTrace )
112- );
113- } else {
114- // Plain text
115- return sprintf (
116- "[%s] %s \n\n%s%s%s%s " ,
117- $ record ->datetime ->format ('Y-m-d H:i:s ' ),
118- strtoupper ($ record ->level ->getName ()),
119- $ record ->message ,
120- str_replace (['```json ' , '``` ' , "\n" ], ['' , '' , "\n" ], $ context ),
121- str_replace (['```json ' , '``` ' , "\n" ], ['' , '' , "\n" ], $ extra ),
122- str_replace (['``` ' , '``` ' ], ['' , '' ], $ stackTrace )
123- );
124- }
125- }
64+ // Encode the record array into JSON
65+ $ message = json_encode ($ recordArray , JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT );
12666
127- protected function escapeMarkdown ( string $ text ): string
128- {
129- $ characters = [ ' _ ' , ' * ' , ' [ ' , ' ] ' , ' ( ' , ' ) ' , ' ~ ' , ' ` ' , ' > ' , ' # ' , ' + ' , ' - ' , ' = ' , ' | ' , ' { ' , ' } ' , ' . ' , ' ! ' ];
130- foreach ( $ characters as $ char ) {
131- $ text = str_replace ( $ char , '\\' . $ char , $ text );
67+ // Send the JSON message
68+ $ this -> sendMessage ( $ message );
69+ } catch ( GuzzleException $ e ) {
70+ // Log the error to the default logger if Telegram fails
71+ error_log ( ' Failed to send Telegram log: ' . $ e -> getMessage () );
13272 }
133-
134- return $ text ;
13573 }
13674
13775 protected function sendMessage (string $ message ): void
13876 {
13977 $ url = "/bot {$ this ->botToken }/sendMessage " ;
14078
141- if ($ this ->splitLongMessages && strlen ($ message ) > $ this ->maxMessageLength ) {
142- $ messages = str_split ($ message , $ this ->maxMessageLength - 100 );
79+ // Format message as JSON code block for better readability in Telegram
80+ $ formattedMessage = "```json \n" .$ message ."\n``` " ;
81+
82+ if ($ this ->splitLongMessages && strlen ($ formattedMessage ) > $ this ->maxMessageLength ) {
83+ $ messages = str_split ($ formattedMessage , $ this ->maxMessageLength - 100 );
14384 foreach ($ messages as $ index => $ partialMessage ) {
14485 $ this ->sendPartialMessage (
14586 $ url ,
14687 sprintf ("(%d/%d) \n%s " , $ index + 1 , count ($ messages ), $ partialMessage )
14788 );
14889 }
14990 } else {
150- $ this ->sendPartialMessage ($ url , $ message );
91+ $ this ->sendPartialMessage ($ url , $ formattedMessage );
15192 }
15293 }
15394
15495 protected function sendPartialMessage (string $ url , string $ message ): void
15596 {
156- $ parseMode = config ('telegramlogs.formatting.parse_mode ' , 'MarkdownV2 ' );
157-
15897 $ payload = [
15998 'chat_id ' => $ this ->chatId ,
16099 'text ' => $ message ,
100+ 'parse_mode ' => 'MarkdownV2 ' ,
161101 'disable_web_page_preview ' => true ,
162102 ];
163103
164- // Only add parse_mode if it's not null (plain text)
165- if ($ parseMode ) {
166- $ payload ['parse_mode ' ] = $ parseMode ;
167- }
168-
169104 if ($ this ->topicId ) {
170105 $ payload ['message_thread_id ' ] = $ this ->topicId ;
171106 }
0 commit comments