1+ import datetime as dt
12import json
23import logging
3- import time
44import unittest
5+ from threading import Event
56
67import requests_mock
78
89from vlogs_handler import VictoriaLogsHandler
10+ from vlogs_handler .handler import JSONEncoderPlus
911
1012
1113@requests_mock .Mocker ()
@@ -15,29 +17,87 @@ def setUp(self):
1517 self .logger = logging .getLogger ("test_logger" )
1618 self .logger .addHandler (self .handler )
1719 self .logger .setLevel (logging .INFO )
20+ self .ready_event = Event ()
1821
1922 def tearDown (self ):
2023 self .logger .removeHandler (self .handler )
2124
22- def test_handler_sends_log (self , m : requests_mock .Mocker ):
25+ def text_callback (self , request , context ):
26+ self .ready_event .set ()
27+ return ""
28+
29+ def test_handler_should_send_normal_log (self , m : requests_mock .Mocker ):
2330 # given
2431 m .register_uri (
2532 "POST" ,
2633 "http://localhost:30123/insert/jsonline" ,
2734 status_code = 200 ,
35+ text = self .text_callback ,
2836 )
2937
3038 # when
3139 self .logger .info ("Alpha" )
32- time .sleep (0.5 )
3340
3441 # then
42+ called_in_time = self .ready_event .wait (timeout = 2.0 )
43+ self .assertTrue (called_in_time )
44+
45+ self .assertEqual (m .call_count , 1 )
46+ got = m .last_request .json () # type: ignore
47+ self .assertEqual (got ["stream" ], "test_logger" )
48+ self .assertEqual (got ["level" ], "INFO" )
49+ self .assertEqual (got ["logger" ], "test_logger" )
50+ self .assertEqual (got ["message" ], "Alpha" )
51+
52+ def test_handler_should_send_log_with_extras (self , m : requests_mock .Mocker ):
53+ # given
54+ m .register_uri (
55+ "POST" ,
56+ "http://localhost:30123/insert/jsonline" ,
57+ status_code = 200 ,
58+ text = self .text_callback ,
59+ )
60+ my_date = dt .datetime (2026 , 1 , 11 , 12 , 15 , 42 , 99 , tzinfo = dt .UTC )
61+
62+ # when
63+ self .logger .info ("Alpha" , extra = {"planet" : "Jupiter" , "deadline" : my_date })
64+
65+ # then
66+ called_in_time = self .ready_event .wait (timeout = 2.0 )
67+ self .assertTrue (called_in_time )
68+
69+ self .assertEqual (m .call_count , 1 )
70+ got = m .last_request .json () # type: ignore
71+ self .assertEqual (got ["message" ], "Alpha" )
72+ self .assertEqual (got ["planet" ], "Jupiter" )
73+ self .assertEqual (got ["deadline" ], "2026-01-11T12:15:42.000099+00:00" )
74+
75+ def test_handler_should_send_exception_log (self , m : requests_mock .Mocker ):
76+ # given
77+ m .register_uri (
78+ "POST" ,
79+ "http://localhost:30123/insert/jsonline" ,
80+ status_code = 200 ,
81+ text = self .text_callback ,
82+ )
83+
84+ # when
85+ try :
86+ _ = 1 / 0
87+ except ZeroDivisionError :
88+ self .logger .exception ("Bravo" )
89+
90+ # then
91+ called_in_time = self .ready_event .wait (timeout = 2.0 )
92+ self .assertTrue (called_in_time )
93+
3594 self .assertEqual (m .call_count , 1 )
36- data = m .last_request .json () # type: ignore
37- self .assertEqual (data ["stream" ], "test_logger" )
38- self .assertEqual (data ["level" ], "INFO" )
39- self .assertEqual (data ["logger" ], "test_logger" )
40- self .assertEqual (data ["message" ], "Alpha" )
95+ got = m .last_request .json () # type: ignore
96+ self .assertEqual (got ["stream" ], "test_logger" )
97+ self .assertEqual (got ["level" ], "ERROR" )
98+ self .assertEqual (got ["logger" ], "test_logger" )
99+ self .assertEqual (got ["message" ], "Bravo" )
100+ self .assertIn ("ZeroDivisionError" , got ["exception" ])
41101
42102
43103@requests_mock .Mocker ()
@@ -49,25 +109,33 @@ def setUp(self):
49109 self .logger = logging .getLogger ("test_logger" )
50110 self .logger .addHandler (self .handler )
51111 self .logger .setLevel (logging .INFO )
112+ self .ready_event = Event ()
52113
53114 def tearDown (self ):
54115 self .logger .removeHandler (self .handler )
55116
117+ def text_callback (self , request , context ):
118+ self .ready_event .set ()
119+ return ""
120+
56121 def test_handler_sends_log (self , m : requests_mock .Mocker ):
57122 # given
58123 m .register_uri (
59124 "POST" ,
60125 "http://localhost:30123/insert/jsonline" ,
61126 status_code = 200 ,
127+ text = self .text_callback ,
62128 )
63129
64130 # when
65131 self .logger .info ("Alpha" )
66132 self .logger .info ("Bravo" )
67133 self .handler .start ()
68- time .sleep (0.5 )
69134
70135 # then
136+ called_in_time = self .ready_event .wait (timeout = 2.0 )
137+ self .assertTrue (called_in_time )
138+
71139 self .assertEqual (m .call_count , 1 )
72140 data = m .last_request .text # type: ignore
73141 lines = data .splitlines ()
@@ -78,3 +146,38 @@ def test_handler_sends_log(self, m: requests_mock.Mocker):
78146
79147 e2 = json .loads (lines [1 ])
80148 self .assertEqual (e2 ["message" ], "Bravo" )
149+ self .assertEqual (e2 ["message" ], "Bravo" )
150+
151+
152+ class TestJSONEncoderPlus (unittest .TestCase ):
153+ def test_should_encode (self ):
154+ # given
155+ def my_func ():
156+ pass
157+
158+ my_date = dt .datetime (2026 , 1 , 11 , 12 , 15 , 42 , 99 , tzinfo = dt .UTC )
159+ data = {
160+ "class" : JSONEncoderPlus ,
161+ "date" : my_date .date (),
162+ "datetime" : my_date ,
163+ "float" : 1.23 ,
164+ "func" : my_func ,
165+ "integer" : 1 ,
166+ "set" : {1 , 2 , 3 },
167+ "text" : "Alpha" ,
168+ }
169+
170+ # when
171+ got = json .loads (json .dumps (data , cls = JSONEncoderPlus ))
172+
173+ # then
174+ self .assertEqual (
175+ got ["class" ], "<class 'vlogs_handler.handler.JSONEncoderPlus'>"
176+ )
177+ self .assertEqual (got ["date" ], "2026-01-11" )
178+ self .assertEqual (got ["datetime" ], "2026-01-11T12:15:42.000099+00:00" )
179+ self .assertEqual (got ["float" ], 1.23 )
180+ self .assertIn ("my_func at" , got ["func" ])
181+ self .assertEqual (got ["integer" ], 1 )
182+ self .assertEqual (got ["set" ], [1 , 2 , 3 ])
183+ self .assertEqual (got ["text" ], "Alpha" )
0 commit comments