# -*- coding: utf-8 -*-
import logging
import sys
import inspect
from functools import wraps
from datetime import datetime
# Python 2兼容性处理
try :
from collections import OrderedDict
except ImportError :
from ordereddict import OrderedDict
class OptimizedFormatter( logging .Formatter ) :
def __init__ ( self , *args, **kwargs) :
logging .Formatter .__init__ ( self , *args, **kwargs)
self ._frame_blacklist = set ( [ id ( inspect .currentframe ( ) ) ] )
def format( self , record) :
try :
# 自动捕获调用信息
self ._inject_frame_info( record)
# 参数格式化
args_str = self ._format_args( record)
if args_str:
record.msg = u"%s ⎸ %s" % ( record.msg , args_str)
# 时间格式化优化
record.asctime = self ._format_time( record)
return logging .Formatter .format ( self , record)
except Exception as e:
return u"⚠️ FORMAT_ERROR | %s | Raw: %s" % ( e, record.msg )
def _inject_frame_info( self , record) :
"""注入调用帧信息"""
if not all ( [ hasattr ( record, a) for a in ( 'module' , 'lineno' , 'funcName' ) ] ) :
frame = self ._find_valid_frame( )
if frame:
record.module = self ._get_module_name( frame)
record.lineno = frame.f_lineno
record.funcName = frame.f_code .co_name
def _find_valid_frame( self ) :
"""查找有效调用帧(兼容Python 2)"""
stack = inspect .stack ( )
for frame_info in reversed ( stack) :
frame = frame_info[ 0 ]
if id ( frame) in self ._frame_blacklist:
continue
if self ._is_logging_frame( frame) :
continue
return frame
return inspect .currentframe ( )
def _is_logging_frame( self , frame) :
"""判断是否日志类自身帧"""
module = inspect .getmodule ( frame)
return module and module.__name__ == __name__
def _get_module_name( self , frame) :
"""安全获取模块名"""
try :
module = inspect .getmodule ( frame)
return module.__name__ if module else 'unknown'
except :
return 'unknown'
def _format_args( self , record) :
"""格式化参数"""
parts = [ ]
# 位置参数
if getattr ( record, 'positional_args' , None ) :
parts.extend ( self ._safe_repr( a) for a in record.positional_args )
# 关键字参数
if getattr ( record, 'extra_kwargs' , None ) :
parts.extend ( u"%s=%s" % ( k, self ._safe_repr( v) )
for k, v in record.extra_kwargs .items ( ) )
return u" ⎸ " .join ( parts) if parts else u""
def _safe_repr( self , value) :
"""安全类型转换(兼容Python 2)"""
try :
if isinstance ( value, dict ) :
return u"{%s}" % u"," .join ( u"%s:%s" % ( self ._safe_repr( k) , self ._safe_repr( v) )
for k, v in value.iteritems ( ) )
if isinstance ( value, ( list , tuple ) ) :
brackets = u"[]" if isinstance ( value, list ) else u"()"
return u"%s%s%s" % ( brackets[ 0 ] , u"," .join ( self ._safe_repr( x) for x in value) , brackets[ 1 ] )
return self ._safe_str( value)
except :
return u"<UNREPRESENTABLE>"
def _safe_str( self , obj) :
"""安全字符串转换(处理Python 2的bytes/unicode)"""
try :
if isinstance ( obj, str ) :
try :
return obj.decode ( 'utf-8' )
except UnicodeDecodeError :
return obj.decode ( 'latin-1' , 'replace' )
if isinstance ( obj, unicode ) :
return obj
return unicode ( str ( obj) , 'utf-8' , errors= 'replace' )
except :
return u"<UNCONVERTIBLE>"
def _format_time( self , record) :
"""高性能时间格式化"""
return datetime .fromtimestamp ( record.created ) .strftime ( '%H:%M:%S' )
class SimpleLogger( object ) :
_initialized = False
@ classmethod
def _ensure_init( cls) :
if not cls._initialized:
root = logging .getLogger ( )
root.setLevel ( logging .DEBUG )
if not root.handlers :
handler = logging .StreamHandler ( )
handler.setFormatter ( OptimizedFormatter(
fmt= u'[%(asctime)s] [%(levelname)s] %(module)s:%(lineno)d ➤ %(funcName)s | %(message)s' ,
datefmt= None # 使用自定义时间格式化
) )
root.addHandler ( handler)
cls._initialized = True
@ classmethod
def _log( cls, level, msg, *args, **kwargs) :
cls._ensure_init( )
try :
# 获取调用上下文
frame = inspect .currentframe ( ) .f_back .f_back
module = inspect .getmodule ( frame) .__name__ if inspect .getmodule ( frame) else 'unknown'
logger = logging .getLogger ( module)
if not logger.handlers :
logger.propagate = True
# 创建LogRecord(兼容Python 2)
record = logger.makeRecord (
name= logger.name ,
level= level,
fn= None ,
lno= None ,
msg= msg,
args= ( ) ,
exc_info= None ,
extra= {
'positional_args' : args,
'extra_kwargs' : kwargs
} ,
func= inspect .getframeinfo ( frame) [ 2 ]
)
# 手动注入行号信息
record.lineno = frame.f_lineno
logger.handle ( record)
except Exception as e:
sys .stderr .write ( u"LOG_ERROR: %s\n " % e)
@ classmethod
def debug( cls, msg, *args, **kwargs) :
cls._log( logging .DEBUG , msg, *args, **kwargs)
@ classmethod
def info( cls, msg, *args, **kwargs) :
cls._log( logging .INFO , msg, *args, **kwargs)
@ classmethod
def warning( cls, msg, *args, **kwargs) :
cls._log( logging .WARNING , msg, *args, **kwargs)
@ classmethod
def error( cls, msg, *args, **kwargs) :
cls._log( logging .ERROR , msg, *args, **kwargs)
if __name__ == "__main__" :
# 测试中文日志
SimpleLogger.info ( u"用户登录" , u"张三" , ip= "192.168.1.100" )
# 测试混合参数
SimpleLogger.error ( "配置错误" , "database" , code = 500 , detail= OrderedDict( [ ( "line" , 42 ) , ( "file" , "app.conf" ) ] ) )
# 测试二进制数据
bad_data = '\x e6\x 97\x a0' # GBK编码的汉字"无"
SimpleLogger.warning ( b"invalid data" , bad_data, sector= [ 0x12 , 0xff , 0x7f ] )
# 测试嵌套参数
SimpleLogger.debug ( "调试信息" , { "key" : [ 1 , 2 , 3 ] } , timeout= 30.5 )
IyAtKi0gY29kaW5nOiB1dGYtOCAtKi0KaW1wb3J0IGxvZ2dpbmcKaW1wb3J0IHN5cwppbXBvcnQgaW5zcGVjdApmcm9tIGZ1bmN0b29scyBpbXBvcnQgd3JhcHMKZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKCiMgUHl0aG9uIDLlhbzlrrnmgKflpITnkIYKdHJ5OgogICAgZnJvbSBjb2xsZWN0aW9ucyBpbXBvcnQgT3JkZXJlZERpY3QKZXhjZXB0IEltcG9ydEVycm9yOgogICAgZnJvbSBvcmRlcmVkZGljdCBpbXBvcnQgT3JkZXJlZERpY3QKCgpjbGFzcyBPcHRpbWl6ZWRGb3JtYXR0ZXIobG9nZ2luZy5Gb3JtYXR0ZXIpOgogICAgZGVmIF9faW5pdF9fKHNlbGYsICphcmdzLCAqKmt3YXJncyk6CiAgICAgICAgbG9nZ2luZy5Gb3JtYXR0ZXIuX19pbml0X18oc2VsZiwgKmFyZ3MsICoqa3dhcmdzKQogICAgICAgIHNlbGYuX2ZyYW1lX2JsYWNrbGlzdCA9IHNldChbaWQoaW5zcGVjdC5jdXJyZW50ZnJhbWUoKSldKQoKICAgIGRlZiBmb3JtYXQoc2VsZiwgcmVjb3JkKToKICAgICAgICB0cnk6CiAgICAgICAgICAgICMg6Ieq5Yqo5o2V6I636LCD55So5L+h5oGvCiAgICAgICAgICAgIHNlbGYuX2luamVjdF9mcmFtZV9pbmZvKHJlY29yZCkKICAgICAgICAgICAgCiAgICAgICAgICAgICMg5Y+C5pWw5qC85byP5YyWCiAgICAgICAgICAgIGFyZ3Nfc3RyID0gc2VsZi5fZm9ybWF0X2FyZ3MocmVjb3JkKQogICAgICAgICAgICBpZiBhcmdzX3N0cjoKICAgICAgICAgICAgICAgIHJlY29yZC5tc2cgPSB1IiVzIOKOuCAlcyIgJSAocmVjb3JkLm1zZywgYXJnc19zdHIpCiAgICAgICAgICAgIAogICAgICAgICAgICAjIOaXtumXtOagvOW8j+WMluS8mOWMlgogICAgICAgICAgICByZWNvcmQuYXNjdGltZSA9IHNlbGYuX2Zvcm1hdF90aW1lKHJlY29yZCkKICAgICAgICAgICAgCiAgICAgICAgICAgIHJldHVybiBsb2dnaW5nLkZvcm1hdHRlci5mb3JtYXQoc2VsZiwgcmVjb3JkKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICAgICAgcmV0dXJuIHUi4pqg77iPIEZPUk1BVF9FUlJPUiB8ICVzIHwgUmF3OiAlcyIgJSAoZSwgcmVjb3JkLm1zZykKCiAgICBkZWYgX2luamVjdF9mcmFtZV9pbmZvKHNlbGYsIHJlY29yZCk6CiAgICAgICAgIiIi5rOo5YWl6LCD55So5bin5L+h5oGvIiIiCiAgICAgICAgaWYgbm90IGFsbChbaGFzYXR0cihyZWNvcmQsIGEpIGZvciBhIGluICgnbW9kdWxlJywgJ2xpbmVubycsICdmdW5jTmFtZScpXSk6CiAgICAgICAgICAgIGZyYW1lID0gc2VsZi5fZmluZF92YWxpZF9mcmFtZSgpCiAgICAgICAgICAgIGlmIGZyYW1lOgogICAgICAgICAgICAgICAgcmVjb3JkLm1vZHVsZSA9IHNlbGYuX2dldF9tb2R1bGVfbmFtZShmcmFtZSkKICAgICAgICAgICAgICAgIHJlY29yZC5saW5lbm8gPSBmcmFtZS5mX2xpbmVubwogICAgICAgICAgICAgICAgcmVjb3JkLmZ1bmNOYW1lID0gZnJhbWUuZl9jb2RlLmNvX25hbWUKCiAgICBkZWYgX2ZpbmRfdmFsaWRfZnJhbWUoc2VsZik6CiAgICAgICAgIiIi5p+l5om+5pyJ5pWI6LCD55So5bin77yI5YW85a65UHl0aG9uIDLvvIkiIiIKICAgICAgICBzdGFjayA9IGluc3BlY3Quc3RhY2soKQogICAgICAgIGZvciBmcmFtZV9pbmZvIGluIHJldmVyc2VkKHN0YWNrKToKICAgICAgICAgICAgZnJhbWUgPSBmcmFtZV9pbmZvWzBdCiAgICAgICAgICAgIGlmIGlkKGZyYW1lKSBpbiBzZWxmLl9mcmFtZV9ibGFja2xpc3Q6CiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBpZiBzZWxmLl9pc19sb2dnaW5nX2ZyYW1lKGZyYW1lKToKICAgICAgICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgICAgIHJldHVybiBmcmFtZQogICAgICAgIHJldHVybiBpbnNwZWN0LmN1cnJlbnRmcmFtZSgpCgogICAgZGVmIF9pc19sb2dnaW5nX2ZyYW1lKHNlbGYsIGZyYW1lKToKICAgICAgICAiIiLliKTmlq3mmK/lkKbml6Xlv5fnsbvoh6rouqvluKciIiIKICAgICAgICBtb2R1bGUgPSBpbnNwZWN0LmdldG1vZHVsZShmcmFtZSkKICAgICAgICByZXR1cm4gbW9kdWxlIGFuZCBtb2R1bGUuX19uYW1lX18gPT0gX19uYW1lX18KCiAgICBkZWYgX2dldF9tb2R1bGVfbmFtZShzZWxmLCBmcmFtZSk6CiAgICAgICAgIiIi5a6J5YWo6I635Y+W5qih5Z2X5ZCNIiIiCiAgICAgICAgdHJ5OgogICAgICAgICAgICBtb2R1bGUgPSBpbnNwZWN0LmdldG1vZHVsZShmcmFtZSkKICAgICAgICAgICAgcmV0dXJuIG1vZHVsZS5fX25hbWVfXyBpZiBtb2R1bGUgZWxzZSAndW5rbm93bicKICAgICAgICBleGNlcHQ6CiAgICAgICAgICAgIHJldHVybiAndW5rbm93bicKCiAgICBkZWYgX2Zvcm1hdF9hcmdzKHNlbGYsIHJlY29yZCk6CiAgICAgICAgIiIi5qC85byP5YyW5Y+C5pWwIiIiCiAgICAgICAgcGFydHMgPSBbXQogICAgICAgICMg5L2N572u5Y+C5pWwCiAgICAgICAgaWYgZ2V0YXR0cihyZWNvcmQsICdwb3NpdGlvbmFsX2FyZ3MnLCBOb25lKToKICAgICAgICAgICAgcGFydHMuZXh0ZW5kKHNlbGYuX3NhZmVfcmVwcihhKSBmb3IgYSBpbiByZWNvcmQucG9zaXRpb25hbF9hcmdzKQogICAgICAgICMg5YWz6ZSu5a2X5Y+C5pWwCiAgICAgICAgaWYgZ2V0YXR0cihyZWNvcmQsICdleHRyYV9rd2FyZ3MnLCBOb25lKToKICAgICAgICAgICAgcGFydHMuZXh0ZW5kKHUiJXM9JXMiICUgKGssIHNlbGYuX3NhZmVfcmVwcih2KSkgCiAgICAgICAgICAgICAgICAgICAgICBmb3IgaywgdiBpbiByZWNvcmQuZXh0cmFfa3dhcmdzLml0ZW1zKCkpCiAgICAgICAgcmV0dXJuIHUiIOKOuCAiLmpvaW4ocGFydHMpIGlmIHBhcnRzIGVsc2UgdSIiCgogICAgZGVmIF9zYWZlX3JlcHIoc2VsZiwgdmFsdWUpOgogICAgICAgICIiIuWuieWFqOexu+Wei+i9rOaNou+8iOWFvOWuuVB5dGhvbiAy77yJIiIiCiAgICAgICAgdHJ5OgogICAgICAgICAgICBpZiBpc2luc3RhbmNlKHZhbHVlLCBkaWN0KToKICAgICAgICAgICAgICAgIHJldHVybiB1Inslc30iICUgdSIsIi5qb2luKHUiJXM6JXMiICUgKHNlbGYuX3NhZmVfcmVwcihrKSwgc2VsZi5fc2FmZV9yZXByKHYpKSAKICAgICAgICAgICAgICAgICAgICAgICAgIGZvciBrLCB2IGluIHZhbHVlLml0ZXJpdGVtcygpKQogICAgICAgICAgICBpZiBpc2luc3RhbmNlKHZhbHVlLCAobGlzdCwgdHVwbGUpKToKICAgICAgICAgICAgICAgIGJyYWNrZXRzID0gdSJbXSIgaWYgaXNpbnN0YW5jZSh2YWx1ZSwgbGlzdCkgZWxzZSB1IigpIgogICAgICAgICAgICAgICAgcmV0dXJuIHUiJXMlcyVzIiAlIChicmFja2V0c1swXSwgdSIsIi5qb2luKHNlbGYuX3NhZmVfcmVwcih4KSBmb3IgeCBpbiB2YWx1ZSksIGJyYWNrZXRzWzFdKQogICAgICAgICAgICByZXR1cm4gc2VsZi5fc2FmZV9zdHIodmFsdWUpCiAgICAgICAgZXhjZXB0OgogICAgICAgICAgICByZXR1cm4gdSI8VU5SRVBSRVNFTlRBQkxFPiIKCiAgICBkZWYgX3NhZmVfc3RyKHNlbGYsIG9iaik6CiAgICAgICAgIiIi5a6J5YWo5a2X56ym5Liy6L2s5o2i77yI5aSE55CGUHl0aG9uIDLnmoRieXRlcy91bmljb2Rl77yJIiIiCiAgICAgICAgdHJ5OgogICAgICAgICAgICBpZiBpc2luc3RhbmNlKG9iaiwgc3RyKToKICAgICAgICAgICAgICAgIHRyeToKICAgICAgICAgICAgICAgICAgICByZXR1cm4gb2JqLmRlY29kZSgndXRmLTgnKQogICAgICAgICAgICAgICAgZXhjZXB0IFVuaWNvZGVEZWNvZGVFcnJvcjoKICAgICAgICAgICAgICAgICAgICByZXR1cm4gb2JqLmRlY29kZSgnbGF0aW4tMScsICdyZXBsYWNlJykKICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShvYmosIHVuaWNvZGUpOgogICAgICAgICAgICAgICAgcmV0dXJuIG9iagogICAgICAgICAgICByZXR1cm4gdW5pY29kZShzdHIob2JqKSwgJ3V0Zi04JywgZXJyb3JzPSdyZXBsYWNlJykKICAgICAgICBleGNlcHQ6CiAgICAgICAgICAgIHJldHVybiB1IjxVTkNPTlZFUlRJQkxFPiIKCiAgICBkZWYgX2Zvcm1hdF90aW1lKHNlbGYsIHJlY29yZCk6CiAgICAgICAgIiIi6auY5oCn6IO95pe26Ze05qC85byP5YyWIiIiCiAgICAgICAgcmV0dXJuIGRhdGV0aW1lLmZyb210aW1lc3RhbXAocmVjb3JkLmNyZWF0ZWQpLnN0cmZ0aW1lKCclSDolTTolUycpCgoKY2xhc3MgU2ltcGxlTG9nZ2VyKG9iamVjdCk6CiAgICBfaW5pdGlhbGl6ZWQgPSBGYWxzZQoKICAgIEBjbGFzc21ldGhvZAogICAgZGVmIF9lbnN1cmVfaW5pdChjbHMpOgogICAgICAgIGlmIG5vdCBjbHMuX2luaXRpYWxpemVkOgogICAgICAgICAgICByb290ID0gbG9nZ2luZy5nZXRMb2dnZXIoKQogICAgICAgICAgICByb290LnNldExldmVsKGxvZ2dpbmcuREVCVUcpCiAgICAgICAgICAgIGlmIG5vdCByb290LmhhbmRsZXJzOgogICAgICAgICAgICAgICAgaGFuZGxlciA9IGxvZ2dpbmcuU3RyZWFtSGFuZGxlcigpCiAgICAgICAgICAgICAgICBoYW5kbGVyLnNldEZvcm1hdHRlcihPcHRpbWl6ZWRGb3JtYXR0ZXIoCiAgICAgICAgICAgICAgICAgICAgZm10PXUnWyUoYXNjdGltZSlzXSBbJShsZXZlbG5hbWUpc10gJShtb2R1bGUpczolKGxpbmVubylkIOKepCAlKGZ1bmNOYW1lKXMgfCAlKG1lc3NhZ2UpcycsCiAgICAgICAgICAgICAgICAgICAgZGF0ZWZtdD1Ob25lICAjIOS9v+eUqOiHquWumuS5ieaXtumXtOagvOW8j+WMlgogICAgICAgICAgICAgICAgKSkKICAgICAgICAgICAgICAgIHJvb3QuYWRkSGFuZGxlcihoYW5kbGVyKQogICAgICAgICAgICBjbHMuX2luaXRpYWxpemVkID0gVHJ1ZQoKICAgIEBjbGFzc21ldGhvZAogICAgZGVmIF9sb2coY2xzLCBsZXZlbCwgbXNnLCAqYXJncywgKiprd2FyZ3MpOgogICAgICAgIGNscy5fZW5zdXJlX2luaXQoKQogICAgICAgIHRyeToKICAgICAgICAgICAgIyDojrflj5bosIPnlKjkuIrkuIvmlocKICAgICAgICAgICAgZnJhbWUgPSBpbnNwZWN0LmN1cnJlbnRmcmFtZSgpLmZfYmFjay5mX2JhY2sKICAgICAgICAgICAgbW9kdWxlID0gaW5zcGVjdC5nZXRtb2R1bGUoZnJhbWUpLl9fbmFtZV9fIGlmIGluc3BlY3QuZ2V0bW9kdWxlKGZyYW1lKSBlbHNlICd1bmtub3duJwoKICAgICAgICAgICAgbG9nZ2VyID0gbG9nZ2luZy5nZXRMb2dnZXIobW9kdWxlKQogICAgICAgICAgICBpZiBub3QgbG9nZ2VyLmhhbmRsZXJzOgogICAgICAgICAgICAgICAgbG9nZ2VyLnByb3BhZ2F0ZSA9IFRydWUKCiAgICAgICAgICAgICMg5Yib5bu6TG9nUmVjb3Jk77yI5YW85a65UHl0aG9uIDLvvIkKICAgICAgICAgICAgcmVjb3JkID0gbG9nZ2VyLm1ha2VSZWNvcmQoCiAgICAgICAgICAgICAgICBuYW1lPWxvZ2dlci5uYW1lLAogICAgICAgICAgICAgICAgbGV2ZWw9bGV2ZWwsCiAgICAgICAgICAgICAgICBmbj1Ob25lLAogICAgICAgICAgICAgICAgbG5vPU5vbmUsCiAgICAgICAgICAgICAgICBtc2c9bXNnLAogICAgICAgICAgICAgICAgYXJncz0oKSwKICAgICAgICAgICAgICAgIGV4Y19pbmZvPU5vbmUsCiAgICAgICAgICAgICAgICBleHRyYT17CiAgICAgICAgICAgICAgICAgICAgJ3Bvc2l0aW9uYWxfYXJncyc6IGFyZ3MsCiAgICAgICAgICAgICAgICAgICAgJ2V4dHJhX2t3YXJncyc6IGt3YXJncwogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIGZ1bmM9aW5zcGVjdC5nZXRmcmFtZWluZm8oZnJhbWUpWzJdCiAgICAgICAgICAgICkKCiAgICAgICAgICAgICMg5omL5Yqo5rOo5YWl6KGM5Y+35L+h5oGvCiAgICAgICAgICAgIHJlY29yZC5saW5lbm8gPSBmcmFtZS5mX2xpbmVubwogICAgICAgICAgICAKICAgICAgICAgICAgbG9nZ2VyLmhhbmRsZShyZWNvcmQpCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgICAgICBzeXMuc3RkZXJyLndyaXRlKHUiTE9HX0VSUk9SOiAlc1xuIiAlIGUpCgogICAgQGNsYXNzbWV0aG9kCiAgICBkZWYgZGVidWcoY2xzLCBtc2csICphcmdzLCAqKmt3YXJncyk6CiAgICAgICAgY2xzLl9sb2cobG9nZ2luZy5ERUJVRywgbXNnLCAqYXJncywgKiprd2FyZ3MpCgogICAgQGNsYXNzbWV0aG9kCiAgICBkZWYgaW5mbyhjbHMsIG1zZywgKmFyZ3MsICoqa3dhcmdzKToKICAgICAgICBjbHMuX2xvZyhsb2dnaW5nLklORk8sIG1zZywgKmFyZ3MsICoqa3dhcmdzKQoKICAgIEBjbGFzc21ldGhvZAogICAgZGVmIHdhcm5pbmcoY2xzLCBtc2csICphcmdzLCAqKmt3YXJncyk6CiAgICAgICAgY2xzLl9sb2cobG9nZ2luZy5XQVJOSU5HLCBtc2csICphcmdzLCAqKmt3YXJncykKCiAgICBAY2xhc3NtZXRob2QKICAgIGRlZiBlcnJvcihjbHMsIG1zZywgKmFyZ3MsICoqa3dhcmdzKToKICAgICAgICBjbHMuX2xvZyhsb2dnaW5nLkVSUk9SLCBtc2csICphcmdzLCAqKmt3YXJncykKCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgIyDmtYvor5XkuK3mlofml6Xlv5cKICAgIFNpbXBsZUxvZ2dlci5pbmZvKHUi55So5oi355m75b2VIiwgdSLlvKDkuIkiLCBpcD0iMTkyLjE2OC4xLjEwMCIpCiAgICAKICAgICMg5rWL6K+V5re35ZCI5Y+C5pWwCiAgICBTaW1wbGVMb2dnZXIuZXJyb3IoIumFjee9rumUmeivryIsICJkYXRhYmFzZSIsIGNvZGU9NTAwLCBkZXRhaWw9T3JkZXJlZERpY3QoWygibGluZSIsIDQyKSwgKCJmaWxlIiwgImFwcC5jb25mIildKSkKICAgIAogICAgIyDmtYvor5Xkuozov5vliLbmlbDmja4KICAgIGJhZF9kYXRhID0gJ1x4ZTZceDk3XHhhMCcgICMgR0JL57yW56CB55qE5rGJ5a2XIuaXoCIKICAgIFNpbXBsZUxvZ2dlci53YXJuaW5nKGIiaW52YWxpZCBkYXRhIiwgYmFkX2RhdGEsIHNlY3Rvcj1bMHgxMiwgMHhmZiwgMHg3Zl0pCiAgICAKICAgICMg5rWL6K+V5bWM5aWX5Y+C5pWwCiAgICBTaW1wbGVMb2dnZXIuZGVidWcoIuiwg+ivleS/oeaBryIsIHsia2V5IjogWzEsIDIsIDNdfSwgdGltZW91dD0zMC41KQ==