Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等。python
什么是摘要算法呢?摘要算法又称哈希算法、散列算法。它经过一个函数,把任意长度的数据转换为一个长度固定的数据串(一般用16进制的字符串表示)。web
摘要算法就是经过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。算法
摘要算法之因此能指出数据是否被篡改过,就是由于摘要函数是一个单向函数,计算f(data)很容易,但经过digest反推data却很是困难。并且,对原始数据作一个bit的修改,都会致使计算出的摘要彻底不一样。sql
咱们以常见的摘要算法MD5为例,计算出一个字符串的MD5值:数据库
1 md5 2 # s1 = '12343254' 3 # ret = hashlib.md5() # 建立一个md5对象 4 # ret.update(s1.encode('utf-8')) # 调用此update方法对参数进行加密 bytes类型 5 # print(ret.hexdigest()) # 获得加密后的结果 定长 6 # 7 # s2 = 'alex12fdsl,.afjsdl;fjksdal;fkdsal;fld;lsdkflas;dkfsda;3' 8 # ret = hashlib.md5() # 建立一个md5对象 9 # ret.update(s2.encode('utf-8')) # 调用此update方法对参数进行加密 bytes类型 10 # print(ret.hexdigest()) # 获得加密后的结果 定长 11 # 12 # s3 = 'alex12fdsl,afjsdl;fjksdal;fkdsal;fld;lsdkflas;dkfsda;3' 13 # ret = hashlib.md5() # 建立一个md5对象 14 # ret.update(s3.encode('utf-8')) # 调用此update方法对参数进行加密 bytes类型 15 # print(ret.hexdigest()) # 获得加密后的结果 定长 16 # 17 # # 不管字符串多长,返回都是定长的数字, 18 # # 同一字符串,MD5值相同. 19 # 20 # #闲人: 将你经常使用的密码 000000 111111 890425 21 # #对应关系表: 22 # # 000000 c108971251713ee7c59db5c097378018 23 # # 撞库: 由于撞库,因此相对不安全,如何解决? 24 # # 123456 e10adc3949ba59abbe56e057f20f883e 25 # 26 # # s4 = '123456' 27 # # ret = hashlib.md5() # 建立一个md5对象 28 # # ret.update(s4.encode('utf-8')) 29 # # print(ret.hexdigest()) # e10adc3949ba59abbe56e057f20f883e 30 # 31 # #解决方式:加盐. 32 # 33 # s3 = '123456' 34 # ret = hashlib.md5('@$1*(^&@^2wqe'.encode('utf-8')) # 建立一个md5对象,加盐 35 # ret.update(s3.encode('utf-8')) # 调用此update方法对参数进行加密 bytes类型 36 # print(ret.hexdigest()) # 获得加密后的结果 定长 c5f8f2288cec341a64b0236649ea0c37 37 38 # 若是黑客盗取到你的固定盐 '@$1*(^&@^2wqe'内容 39 40 # 变成随机的盐: 41 # username = '爽妹' 42 # password = '123456' 43 44 # ret = hashlib.md5(username[::-1].encode('utf-8')) 45 # ret.update(password.encode('utf-8')) 46 # print(ret.hexdigest())
MD5是最多见的摘要算法,速度很快,生成结果是固定的128 bit字节,一般用一个32位的16进制字符串表示。另外一种常见的摘要算法是SHA1,调用SHA1和调用MD5彻底相似:django
1 #sha 系列 2 # hashlib.sha1() # sha1 与md5 级别相同,可是sha1比md5 更安全一些, 3 # ret = hashlib.sha1() 4 # ret.update('123456'.encode('utf-8')) 5 # print(ret.hexdigest()) # 7c4a8d09ca3762af61e59520943dc26494f8941b 6 7 # ret = hashlib.sha512() #级别最高,效率低,安全性最大 8 # ret.update('123456'.encode('utf-8')) 9 # print(ret.hexdigest()) # ba3253876aed6bc22d4a6ff53d8406c6ad864195ed144ab5c87621b6c233b548baeae6956df346ec8c17f5ea10f35ee3cbc514797ed7ddd3145464e2a0bab413 10 11 # 文件的校验 12 # 对于小文件能够,可是超大的文件内存受不了,(下面具体代码解决) 13 # def func(file_name): 14 # with open(file_name,mode='rb') as f1: 15 # ret = hashlib.md5() 16 # ret.update(f1.read()) 17 # return ret.hexdigest() 18 # 19 # print(func('hashlib_file')) 20 # print(func('hashlib_file1')) 21 22 # s1 = 'I am 旭哥, 都别惹我.... 不服你试试' 23 # ret = hashlib.md5() 24 # ret.update(s1.encode('utf-8')) 25 # print(ret.hexdigest()) # 15f614e4f03312320cc5cf83c8b2706f 26 27 # s1 = 'I am 旭哥, 都别惹我.... 不服你试试' 28 # ret = hashlib.md5() 29 # ret.update('I am'.encode('utf-8')) 30 # ret.update(' 旭哥, '.encode('utf-8')) 31 # ret.update('都别惹我....'.encode('utf-8')) 32 # ret.update(' 不服你试试'.encode('utf-8')) 33 # print(ret.hexdigest()) # 15f614e4f03312320cc5cf83c8b2706f 34 35 # def func(file_name): 36 # with open(file_name,mode='rb') as f1: 37 # ret = hashlib.md5() 38 # while True: 39 # content = f1.read(1024) 40 # if content: 41 # ret.update(content) 42 # else: 43 # break 44 # return ret.hexdigest() 45 # print(func('hashlib_file')) 46 # print(func('hashlib_file1'))
SHA1的结果是160 bit字节,一般用一个40位的16进制字符串表示。比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法越慢,并且摘要长度更长。windows
摘要算法应用安全
任何容许用户登陆的网站都会存储用户登陆的用户名和口令。如何存储用户名和口令呢?方法是存到数据库表中:服务器
name | password --------+---------- michael | 123456 bob | abc999 alice | alice2008
若是以明文保存用户口令,若是数据库泄露,全部用户的口令就落入黑客的手里。此外,网站运维人员是能够访问数据库的,也就是能获取到全部用户的口令。正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要,好比MD5:session
username | password ---------+--------------------------------- michael | e10adc3949ba59abbe56e057f20f883e bob | 878ef96e86145580c38c87f0410ad153 alice | 99b1c2188db85afee403b1536010c2c9
考虑这么个状况,不少用户喜欢用123456,888888,password这些简单的口令,因而,黑客能够事先计算出这些经常使用口令的MD5值,获得一个反推表:
'e10adc3949ba59abbe56e057f20f883e': '123456' '21218cca77804d2ba1922c33e0151105': '888888' '5f4dcc3b5aa765d61d8327deb882cf99': 'password'
这样,无需破解,只须要对比数据库的MD5,黑客就得到了使用经常使用口令的用户帐号。
对于用户来说,固然不要使用过于简单的口令。可是,咱们可否在程序设计上对简单口令增强保护呢?
因为经常使用口令的MD5值很容易被计算出来,因此,要确保存储的用户口令不是那些已经被计算出来的经常使用口令的MD5,这一方法经过对原始口令加一个复杂字符串来实现,俗称“加盐”
hashlib.md5("salt".encode("utf8"))
通过Salt处理的MD5口令,只要Salt不被黑客知道,即便用户输入简单口令,也很难经过MD5反推明文口令。
可是若是有两个用户都使用了相同的简单口令好比123456,在数据库中,将存储两条相同的MD5值,这说明这两个用户的口令是同样的。有没有办法让使用相同口令的用户存储不一样的MD5呢?
若是假定用户没法修改登陆名,就能够经过把登陆名做为Salt的一部分来计算MD5,从而实现相同口令的用户也存储不一样的MD5。
摘要算法在不少地方都有普遍的应用。要注意摘要算法不是加密算法,不能用于加密(由于没法经过摘要反推明文),只能用于防篡改,可是它的单向计算特性决定了能够在不存储明文口令的状况下验证用户口令。
configparser模块:
该模块适用于配置文件的格式与windows ini文件相似,能够包含一个或多个节(section),每一个节能够有多个参数(键=值)。
1 """ 2 Django settings for webwx project. 3 4 Generated by 'django-admin startproject' using Django 1.10.3. 5 6 For more information on this file, see 7 https://docs.djangoproject.com/en/1.10/topics/settings/ 8 9 For the full list of settings and their values, see 10 https://docs.djangoproject.com/en/1.10/ref/settings/ 11 """ 12 13 import os 14 15 # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 18 19 # Quick-start development settings - unsuitable for production 20 # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ 21 22 # SECURITY WARNING: keep the secret key used in production secret! 23 SECRET_KEY = 'mpn^n-s-&+ckg_)gl4sp^@8=89us&@*^r1c_81#x-5+$)rf8=3' 24 25 # SECURITY WARNING: don't run with debug turned on in production! 26 DEBUG = True 27 28 ALLOWED_HOSTS = [] 29 30 31 # Application definition 32 33 INSTALLED_APPS = [ 34 'django.contrib.admin', 35 'django.contrib.auth', 36 'django.contrib.contenttypes', 37 'django.contrib.sessions', 38 'django.contrib.messages', 39 'django.contrib.staticfiles', 40 'web', 41 ] 42 43 MIDDLEWARE = [ 44 'django.middleware.security.SecurityMiddleware', 45 'django.contrib.sessions.middleware.SessionMiddleware', 46 'django.middleware.common.CommonMiddleware', 47 # 'django.middleware.csrf.CsrfViewMiddleware', 48 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 'django.contrib.messages.middleware.MessageMiddleware', 50 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 ] 52 53 ROOT_URLCONF = 'webwx.urls' 54 55 TEMPLATES = [ 56 { 57 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 'DIRS': [os.path.join(BASE_DIR, 'templates')] 59 , 60 'APP_DIRS': True, 61 'OPTIONS': { 62 'context_processors': [ 63 'django.template.context_processors.debug', 64 'django.template.context_processors.request', 65 'django.contrib.auth.context_processors.auth', 66 'django.contrib.messages.context_processors.messages', 67 ], 68 }, 69 }, 70 ] 71 72 WSGI_APPLICATION = 'webwx.wsgi.application' 73 74 75 # Database 76 # https://docs.djangoproject.com/en/1.10/ref/settings/#databases 77 78 DATABASES = { 79 'default': { 80 'ENGINE': 'django.db.backends.sqlite3', 81 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 } 83 } 84 85 86 # Password validation 87 # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators 88 89 AUTH_PASSWORD_VALIDATORS = [ 90 { 91 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 }, 93 { 94 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 }, 96 { 97 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 }, 99 { 100 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 }, 102 ] 103 104 105 # Internationalization 106 # https://docs.djangoproject.com/en/1.10/topics/i18n/ 107 108 LANGUAGE_CODE = 'en-us' 109 110 TIME_ZONE = 'UTC' 111 112 USE_I18N = True 113 114 USE_L10N = True 115 116 USE_TZ = True 117 118 119 # Static files (CSS, JavaScript, Images) 120 # https://docs.djangoproject.com/en/1.10/howto/static-files/ 121 122 STATIC_URL = '/static/' 123 STATICFILES_DIRS = ( 124 os.path.join(BASE_DIR,'static'), 125 ) 126 127 Django的配置文件举例
来看一个好多软件的常见文档格式以下:
1 [DEFAULT] 2 ServerAliveInterval = 45 3 Compression = yes 4 CompressionLevel = 9 5 ForwardX11 = yes 6 7 [bitbucket.org] 8 User = hg 9 10 [topsecret.server.com] 11 Port = 50022 12 ForwardX11 = no
若是想用python生成一个这样的文档怎么作呢?
1 import configparser 2 3 config = configparser.ConfigParser() 4 5 config["DEFAULT"] = {'ServerAliveInterval': '45', 6 'Compression': 'yes', 7 'CompressionLevel': '9', 8 'ForwardX11':'yes' 9 } 10 11 config['bitbucket.org'] = {'User':'hg'} 12 13 config['topsecret.server.com'] = {'Host Port':'50022','ForwardX11':'no'} 14 15 with open('example.ini', 'w') as configfile: 16 17 config.write(configfile)
查找文件
1 import configparser 2 3 config = configparser.ConfigParser() 4 5 #---------------------------查找文件内容,基于字典的形式 6 7 print(config.sections()) # [] 8 9 config.read('example.ini') 10 11 print(config.sections()) # ['bitbucket.org', 'topsecret.server.com'] 12 13 print('bytebong.com' in config) # False 14 print('bitbucket.org' in config) # True 15 16 17 print(config['bitbucket.org']["user"]) # hg 18 19 print(config['DEFAULT']['Compression']) #yes 20 21 print(config['topsecret.server.com']['ForwardX11']) #no 22 23 24 print(config['bitbucket.org']) #<Section: bitbucket.org> 25 26 for key in config['bitbucket.org']: # 注意,有default会默认default的键 27 print(key) 28 29 print(config.options('bitbucket.org')) # 同for循环,找到'bitbucket.org'下全部键 30 31 print(config.items('bitbucket.org')) #找到'bitbucket.org'下全部键值对 32 33 print(config.get('bitbucket.org','compression')) # yes get方法Section下的key对应的value
增删改操做
1 import configparser 2 3 config = configparser.ConfigParser() 4 5 config.read('example.ini') 6 7 config.add_section('yuan') 8 9 10 11 config.remove_section('bitbucket.org') 12 config.remove_option('topsecret.server.com',"forwardx11") 13 14 15 config.set('topsecret.server.com','k1','11111') 16 config.set('yuan','k2','22222') 17 18 config.write(open('new2.ini', "w"))
1 import logging 2 logging.debug('debug message') 3 logging.info('info message') 4 logging.warning('warning message') 5 logging.error('error message') 6 logging.critical('critical message')
默认状况下Python的logging模块将日志打印到了标准输出中,且只显示了大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG),默认的日志格式为日志级别:Logger名称:用户输出消息。
灵活配置日志级别,日志格式,输出位置:
1 import logging 2 logging.basicConfig(level=logging.DEBUG, 3 format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', 4 datefmt='%a, %d %b %Y %H:%M:%S', 5 filename='/tmp/test.log', 6 filemode='w') 7 8 logging.debug('debug message') 9 logging.info('info message') 10 logging.warning('warning message') 11 logging.error('error message') 12 logging.critical('critical message')
1 logging.basicConfig()函数中可经过具体参数来更改logging模块默认行为,可用参数有: 2 3 filename:用指定的文件名建立FiledHandler,这样日志会被存储在指定的文件中。 4 filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。 5 format:指定handler使用的日志显示格式。 6 datefmt:指定日期时间格式。 7 level:设置rootlogger(后边会讲解具体概念)的日志级别 8 stream:用指定的stream建立StreamHandler。能够指定输出到sys.stderr,sys.stdout或者文件(f=open(‘test.log’,’w’)),默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。 9 10 format参数中可能用到的格式化串: 11 %(name)s Logger的名字 12 %(levelno)s 数字形式的日志级别 13 %(levelname)s 文本形式的日志级别 14 %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有 15 %(filename)s 调用日志输出函数的模块的文件名 16 %(module)s 调用日志输出函数的模块名 17 %(funcName)s 调用日志输出函数的函数名 18 %(lineno)d 调用日志输出函数的语句所在的代码行 19 %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示 20 %(relativeCreated)d 输出日志信息时的,自Logger建立以 来的毫秒数 21 %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒 22 %(thread)d 线程ID。可能没有 23 %(threadName)s 线程名。可能没有 24 %(process)d 进程ID。可能没有 25 %(message)s用户输出的消息
logger对象配置
1 import logging 2 3 logger = logging.getLogger() 4 # 建立一个handler,用于写入日志文件 5 fh = logging.FileHandler('test.log',encoding='utf-8') 6 7 # 再建立一个handler,用于输出到控制台 8 ch = logging.StreamHandler() 9 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 10 fh.setLevel(logging.DEBUG) 11 12 fh.setFormatter(formatter) 13 ch.setFormatter(formatter) 14 logger.addHandler(fh) #logger对象能够添加多个fh和ch对象 15 logger.addHandler(ch) 16 17 logger.debug('logger debug message') 18 logger.info('logger info message') 19 logger.warning('logger warning message') 20 logger.error('logger error message') 21 logger.critical('logger critical message')
logging库提供了多个组件:Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。另外,能够经过:logger.setLevel(logging.Debug)设置级别,固然,也能够经过
fh.setLevel(logging.Debug)单对文件流设置某个级别。
logging总结:
1 #log 日志: 2 #何时用到日志? 3 #生活中: 4 # 1, 公司员工信息工号等等须要日志. 5 # 2, 淘宝,京东 你的消费信息,浏览记录等等都记录日志中,个性化推荐. 6 # 3, 头条个性化设置(爱好记录的日志中). 7 8 # 工做上: 9 # 运维人员,任何员工对服务器作过的任何操做,都会记录到日志中. 10 # 若是你要是从事运维开发的工做,各处都须要日志. 11 # debug模式,须要依靠日志的. 12 #定时收集信息,也要记录日志. 13 14 # logging 模块是辅助你记录日志的,不是自动记录日志的. 15 # 低配版,logging 16 # 高配版,logger 对象 17 18 # 低配版,logging 19 import logging 20 # 等级是一层一层升高的. 21 # logging.basicConfig(level=logging.ERROR) 22 # # level=logging.DEBUG 设置显示报错的级别. 23 # logging.debug('debug message') # 调试信息 24 # logging.info('info message') # 正常信息 25 # logging.warning('warning message') # 警告信息:代码虽然不报错,可是警告你写的不规范,必须改. 26 # logging.error('error message') # 错误信息. 27 # logging.critical('critical message') # 严重错误信息. 28 29 # 用法实例: 30 # try: 31 # num = input('>>>请输入') 32 # num = int(num) 33 # except ValueError: 34 # logging.error('出现了 %s' % ValueError) 35 36 # 1,调整格式.(完善报错信息) 37 38 # logging.basicConfig(level=logging.DEBUG, 39 # format='%(asctime)s %(filename)s (line:%(lineno)d) %(levelname)s %(message)s', 40 # ) 41 # # level=logging.DEBUG 设置显示报错的级别. 42 # logging.debug('debug message') # 调试信息 43 # logging.info('info message') # 正常信息 44 # logging.warning('warning message') # 警告信息:代码虽然不报错,可是警告你写的不规范,必须改. 45 # logging.error('error message') # 错误信息. 46 # logging.critical('critical message') # 严重错误信息. 47 logging.basicConfig(level=logging.DEBUG, 48 format='%(asctime)s %(filename)s (line:%(lineno)d) %(levelname)s %(message)s', 49 # datefmt='%a, %d %b %Y %H:%M:%S', # 设置时间格式 50 filename='low_logging.log', 51 # filemode='w', 52 ) 53 logging.warning('warning 警告错误!!!!') # 警告信息:代码虽然不报错,可是警告你写的不规范,必须改. 54 logging.error('error message') # 错误信息. 55 logging.critical('critical message') # 严重错误信息. 56 # level=logging.DEBUG 设置显示报错的级别. 57 # try: 58 # num = input('>>>请输入') 59 # num = int(num) 60 # except Exception as e: 61 # logging.warning(e) # 警告信息:代码虽然不报错,可是警告你写的不规范,必须改. 62 # logging.error(e) # 错误信息. 63 # logging.critical(e) # 严重错误信息. 64 65 # low logging 缺点: 66 # 1,写入文件 打印日志不能同时进行. 67 # 2 ,写入文件时文件编码方式为gbk..
1 # 高配版 2 # 初版:只输入文件中. 3 # import logging 4 # logger = logging.getLogger() # 建立logger对象. 5 # fh = logging.FileHandler('高配版logging.log',encoding='utf-8') # 建立文件句柄 6 # 7 # # 吸星大法 8 # logger.addHandler(fh) 9 # 10 # logging.debug('debug message') 11 # logging.info('info message') 12 # logging.warning('warning message') 13 # logging.error('error message') 14 # logging.critical('critical message') 15 16 # 第二版:文件和屏幕都存在. 17 # import logging 18 # logger = logging.getLogger() # 建立logger对象. 19 # fh = logging.FileHandler('高配版logging.log',encoding='utf-8') # 建立文件句柄 20 # sh = logging.StreamHandler() #产生了一个屏幕句柄 21 # 22 # # 吸星大法 23 # logger.addHandler(fh) #添加文件句柄 24 # logger.addHandler(sh) #添加屏幕句柄 25 # 26 # 27 # logging.debug('debug message') 28 # logging.info('info message') 29 # logging.warning('warning message') 30 # logging.error('error message') 31 # logging.critical('critical message') 32 33 # 第三版:文件和屏幕都存在的基础上 设置显示格式. 34 # import logging 35 # logger = logging.getLogger() # 建立logger对象. 36 # fh = logging.FileHandler('高配版logging.log',encoding='utf-8') # 建立文件句柄 37 # sh = logging.StreamHandler() #产生了一个屏幕句柄 38 # formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 39 # 40 # 41 # # 吸星大法 42 # logger.addHandler(fh) #添加文件句柄 43 # logger.addHandler(sh) #添加屏幕句柄 44 # sh.setFormatter(formatter) # 设置屏幕格式 45 # fh.setFormatter(formatter) # 设置文件的格式 (这两个按照需求能够单独设置) 46 # 47 # 48 # logging.debug('debug message') 49 # logging.info('info message') 50 # logging.warning('warning message') 51 # logging.error('error message') 52 # logging.critical('critical message') 53 54 #第四版 文件和屏幕都存在的基础上 设置显示格式.而且设置日志水平. 55 # import logging 56 # logger = logging.getLogger() # 建立logger对象. 57 # fh = logging.FileHandler('高配版logging.log',encoding='utf-8') # 建立文件句柄 58 # sh = logging.StreamHandler() #产生了一个屏幕句柄 59 # formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 60 # # logger.setLevel(logging.DEBUG) 61 # #若是你对logger对象设置日志等级.那么文件和屏幕都设置了. 62 # #总开关 默认从warning开始,若是想设置分开关:必需要从他更高级:(ERROR,critical)从这来个开始. 63 # 64 # # 吸星大法 65 # logger.addHandler(fh) #添加文件句柄 66 # logger.addHandler(sh) #添加屏幕句柄 67 # sh.setFormatter(formatter) # 设置屏幕格式 68 # fh.setFormatter(formatter) # 设置文件的格式 (这两个按照需求能够单独设置) 69 # fh.setLevel(logging.DEBUG) 70 # 71 # logging.debug('debug message') 72 # logging.info('info message') 73 # logging.warning('warning message') 74 # logging.error('error message') 75 # logging.critical('critical message')