66## Standard Library
77from dataclasses import dataclass
88import datetime
9+ import enum
910import io
1011import json
1112import logging
1213import sys
1314import traceback
15+ from types import TracebackType
1416from typing import Any , Generator
17+ import uuid
1518
1619## Installed
1720from freezegun import freeze_time
@@ -81,6 +84,34 @@ def get_traceback_from_exception_followed_by_log_call(env_: LoggingEnvironment)
8184 return str_traceback
8285
8386
87+ class SomeClass :
88+ def __init__ (self , thing : int ):
89+ self .thing = thing
90+ return
91+
92+
93+ @dataclass
94+ class SomeDataclass :
95+ things : str
96+ stuff : int
97+ junk : bool
98+
99+
100+ try :
101+ raise ValueError
102+ except ValueError as e :
103+ STATIC_TRACEBACK = e .__traceback__
104+ del e
105+
106+
107+ class MultiEnum (enum .Enum ):
108+ NONE = None
109+ BOOL = False
110+ STR = "somestring"
111+ INT = 99
112+ BYTES = b"somebytes"
113+
114+
84115### TESTS
85116### ============================================================================
86117def test_merge_record_extra ():
@@ -344,13 +375,114 @@ def test_default_encoder_with_timestamp(env: LoggingEnvironment, class_: type[Ba
344375 env .set_formatter (class_ (timestamp = True ))
345376
346377 env .logger .info ("Hello" )
347- print (env .buffer .getvalue ())
348378 log_json = env .load_json ()
349379
350380 assert log_json ["timestamp" ] == "2017-07-14T02:40:00+00:00"
351381 return
352382
353383
384+ @pytest .mark .parametrize ("class_" , ALL_FORMATTERS )
385+ @pytest .mark .parametrize (
386+ ["obj" , "type_" ],
387+ [
388+ ("somestring" , str ),
389+ (1234 , int ),
390+ (1234.5 , float ),
391+ (False , bool ),
392+ (None , type (None )),
393+ (b"somebytes" , str ),
394+ (datetime .time (16 , 45 , 30 , 100 ), str ),
395+ (datetime .date .today (), str ),
396+ (datetime .datetime .utcnow (), str ),
397+ (uuid .uuid4 (), str ),
398+ (Exception , str ),
399+ (Exception ("Foo occurred" ), str ),
400+ (BaseException , str ),
401+ (BaseException ("BaseFoo occurred" ), str ),
402+ (STATIC_TRACEBACK , str ),
403+ (SomeDataclass (things = "le_things" , stuff = 99 , junk = False ), dict ),
404+ (SomeDataclass , str ),
405+ (SomeClass , str ),
406+ (SomeClass (1234 ), str ),
407+ (MultiEnum .NONE , type (None )),
408+ (MultiEnum .BOOL , bool ),
409+ (MultiEnum .STR , str ),
410+ (MultiEnum .INT , int ),
411+ (MultiEnum .BYTES , str ),
412+ (MultiEnum , str ),
413+ ],
414+ )
415+ def test_common_types_encoded (
416+ env : LoggingEnvironment , class_ : type [BaseJsonFormatter ], obj : object , type_ : type
417+ ):
418+ ## Known bad cases
419+ if class_ is JsonFormatter :
420+ if obj is SomeDataclass or isinstance (obj , SomeDataclass ) or isinstance (obj , enum .Enum ):
421+ pytest .xfail ()
422+
423+ if pythonjsonlogger .ORJSON_AVAILABLE and class_ is OrjsonFormatter :
424+ if (
425+ obj is Exception
426+ or obj is BaseException
427+ or isinstance (obj , BaseException )
428+ or obj is SomeDataclass
429+ or obj is SomeClass
430+ or isinstance (obj , SomeClass )
431+ or isinstance (obj , bytes )
432+ or isinstance (obj , TracebackType )
433+ or isinstance (obj , enum .EnumMeta )
434+ or obj is MultiEnum .BYTES
435+ ):
436+ pytest .xfail ()
437+
438+ if pythonjsonlogger .MSGSPEC_AVAILABLE and class_ is MsgspecFormatter :
439+ if (
440+ obj is Exception
441+ or obj is BaseException
442+ or isinstance (obj , BaseException )
443+ or obj is SomeDataclass
444+ or obj is SomeClass
445+ or isinstance (obj , SomeClass )
446+ or isinstance (obj , TracebackType )
447+ or isinstance (obj , enum .EnumMeta )
448+ or (
449+ isinstance (obj , enum .Enum )
450+ and obj in {MultiEnum .BYTES , MultiEnum .NONE , MultiEnum .BOOL }
451+ )
452+ ):
453+ pytest .xfail ()
454+
455+ ## Test
456+ env .set_formatter (class_ ())
457+ extra = {
458+ "extra" : obj ,
459+ "extra_dict" : {"item" : obj },
460+ "extra_list" : [obj ],
461+ }
462+ env .logger .info ("hello" , extra = extra )
463+ log_json = env .load_json ()
464+
465+ assert isinstance (log_json ["extra" ], type_ )
466+ assert isinstance (log_json ["extra_dict" ]["item" ], type_ )
467+ assert isinstance (log_json ["extra_list" ][0 ], type_ )
468+ return
469+
470+
471+ @pytest .mark .parametrize ("class_" , ALL_FORMATTERS )
472+ def test_custom_default (env : LoggingEnvironment , class_ : type [BaseJsonFormatter ]):
473+ def custom_default (obj ):
474+ if isinstance (obj , SomeClass ):
475+ return {"TYPE" : obj .thing }
476+ return None
477+
478+ env .set_formatter (class_ (json_default = custom_default )) # type: ignore[call-arg]
479+ env .logger .info ("hello" , extra = {"extra" : SomeClass (999 )})
480+ log_json = env .load_json ()
481+
482+ assert log_json ["extra" ] == {"TYPE" : 999 }
483+ return
484+
485+
354486## JsonFormatter Specific
355487## -----------------------------------------------------------------------------
356488def test_json_default_encoder (env : LoggingEnvironment ):
@@ -372,21 +504,6 @@ def test_json_default_encoder(env: LoggingEnvironment):
372504 return
373505
374506
375- def test_json_custom_default (env : LoggingEnvironment ):
376- def custom (o ):
377- return "very custom"
378-
379- env .set_formatter (JsonFormatter (json_default = custom ))
380-
381- msg = {"adate" : datetime .datetime (1999 , 12 , 31 , 23 , 59 ), "normal" : "value" }
382- env .logger .info (msg )
383- log_json = env .load_json ()
384-
385- assert log_json ["adate" ] == "very custom"
386- assert log_json ["normal" ] == "value"
387- return
388-
389-
390507def test_json_ensure_ascii_true (env : LoggingEnvironment ):
391508 env .set_formatter (JsonFormatter ())
392509 env .logger .info ("Привет" )
0 commit comments