Sanic学习——框架封装

上一回: Sanic学习——初识Sanic

俗话说,学东西就要多用,Sanic也在不停的把玩,不过在玩的过程中,有些不是很方便。

发现问题

1、服务启动问题
既然是一个框架,需要保持服务的运行,所以在服务开发好后,需要通过nohup等方式后台执行

nohup python3 server.py >> service.log 2>&1 &

每次通过这种方式启动是不是优点麻烦想通过脚本或者其他方式封装一下,简化我们启动服务的难度。
2、log日志输出问题
sanic有相应的日志记录,可以查看 sanic/log.py,从源代码可以看到,sanic有三种类型的日志

logger = logging.getLogger("sanic.root")
error_logger = logging.getLogger("sanic.error")
access_logger = logging.getLogger("sanic.access")

不过这三种日志默认都是console,打印到命令行的,当然我们可以通过nohup来按天分割日志,不过使用起来也不是很方便,不过我们可以通过在初始化的时候传递log_conf来修改日志的配置。

app = Sanic(log_config=log_config)

遇到这两个问题,我就在想是否可以封装一下,是sanic框架更好用点。当然这只是我的想法,如果你觉得这样挺好,也是ok的。

解决问题

1、通过bash脚本进行服务的启动
2、封装Sanic初始化工作
3、初始化时,解决log配置问题

那么开始解决问题吧

1、首先解决log配置问题

新建utils/logUtil.py文件

import logging
import sys

logger = logging.getLogger("sanic.access")
# 获取配置
def _make_log_file(filename, count=30):
    return dict(
        version=1,
        disable_existing_loggers=False,
        loggers={
            "sanic.access": {
                "level": "DEBUG", # log日志等级
                "handlers": ["access_file_handler"], # log使用的handler
                "propagate": False, #不将日志记录传递给祖先
                "qualname": "sanic.access", 
            },
            "sanic.root": {"level": "INFO", "handlers": ["root_console_handler"]},
        },
        handlers={
            "access_file_handler": {
                "class": "logging.handlers.TimedRotatingFileHandler", #hander名称
                "formatter": "access", # 格式化formater名称
                "filename": filename, # 输出到的文件名
                "when": "midnight", # 分割日志时间
                "backupCount": count, #日志保留次数,目前这样配置可以保留30天
                "encoding": "utf-8" #日志编码
            },
            "root_console_handler": {
                "class": "logging.StreamHandler",
                "formatter": "generic",
                "stream": sys.stdout,
            },
        },
        formatters={
            "generic": {
                "format": "%(asctime)s [%(process)d] [%(levelname)s] %(message)s",
                "class": "logging.Formatter",
            },
            "access": {
                "format": "%(asctime)s [%(levelname)s][%(process)d]: (%(filename)s:%(lineno)d) %(message)s", #日志输出格式
                "class": "logging.Formatter",
            },
        },
    )
# 根据参数获取存储的文件名
def get_log_filename(logfile, index):
    if logfile.find("{}") >= 0:
        return True, logfile.format(index)

    if logfile.find("%") >= 0:
        return True, logfile % index

    return False, logfile

# 设置logging配置以及等级
def set_logger(param=None, filename=None, level=logging.DEBUG, count=30):
    if filename:
        param = _make_log_file(filename, count)
    if param and isinstance(param, (dict, )):
        logging.config.dictConfig(param)

    level and logger.setLevel(level)

看了这一段代码,可能有些不知所云。
Sanic在源代码中使用了access_log,需要通过设置access_log=True打开,目前可以使用Sanic的access_log进行配置,方便后续进行debug调试。

logging的配置主要包含三个部分,1、logger(定义日志)2、handler(定义日志输出形式)3、formatter(定义日志输出格式)官网文档
目前使用access log,通过文件形式,在24点进行日志切分,保留日志30天。

2、其次开始封装Sanic初始化

新建run_server.py

from argparse import ArgumentParser
from importlib import import_module

from sanic import Sanic

from utils.logUtil import get_log_filename, set_logger, logger

if __name__ == '__main__':
    parser = ArgumentParser(prog="sanic")
    parser.add_argument("--id", dest="id", type=int, default=0)
    parser.add_argument("--host", dest="host", type=str, default="127.0.0.1")
    parser.add_argument("--port", dest="port", type=int, default=8000)
    parser.add_argument("--workers", dest="workers", type=int, default=1)
    parser.add_argument("--debug", dest="debug", action="store_true")
    parser.add_argument("--module", dest="module", help="importable path of app")
    parser.add_argument("--logfile", dest="logfile", type=str, default="service.%d.log")
    parser.add_argument("--loglevel", dest="loglevel", type=str, default="INFO")
    parser.add_argument("--logcount", dest="logcount", type=int, default=30)
    parser.add_argument("--accesslog", dest="accesslog", action="store_true")
    parser.add_argument("--env", dest="env", type=str, default="prod|qa|uat etc")
    parser.add_argument("--cert", dest="cert", type=str, help="location of certificate for SSL")
    parser.add_argument("--key", dest="key", type=str, help="location of keyfile for SSL.")
    args = parser.parse_args()

    try:
        module_parts = args.module.split(".")
        module_name = ".".join(module_parts[:-1])
        app_name = module_parts[-1]

        module = import_module(module_name)
        app = getattr(module, app_name, None)
        if not isinstance(app, Sanic):
            raise ValueError(
                f"{module_name}.{app_name} is {type(app).__name__} not a Sanic app,"
            )
        dummy, fn = get_log_filename(args.logfile, args.id)
        set_logger(filename=fn, level=args.loglevel, count=args.logcount)

        app.cmd_args = args
        ssl = None
        if args.cert and args.key:
            ssl = {"cert": args.cert, "key": args.key}
        logger.info(f"Module {module_name}.{app_name} is ready...")
        app.run(
            host=args.host,
            port=args.port,
            workers=args.workers,
            debug=args.debug,
            access_log=args.accesslog,
            ssl=None
        )
        logger.info(f"Module {module_name}.{app_name} is terminated.")
    except Exception as ex:
        logger.exception(ex)
        logger.info(f"Failed to run app: {ex}")

看这一段代码是不是又晕了,主要包含4个步骤
1、接受输入参数
2、通过module参数获取到Sanic对象
3、修改日志配置
4、启动sanic服务
那么为什么要有1,2两个步骤呢?主要是因为,想要尽可能的将输入参数通过脚本传递过来,部分参数是参考sanic.__main__.py,如host,port,workers,debug,module,cert, key等。
id:在启动服务中,可能我们会启动多个进程来运行服务,id用来记录当前是哪一个进程,方便日志输出到对应的文件中。
logfile,loglevel,logcount,accesslog:这几个参数都是日志的相关配置,这里我就不过多赘述了。
env:对应的是当前我们使用的是什么环境,以便我们可以从sanic实例中拿到env环境变量进行对应的设置。
######3、通过脚本启动服务

#!/bin/bash
# 定义参数
SANICMODULE=main.app
HOST=127.0.0.1
BASEPORT=12000
WORKERS=1
DEBUG=0
ENV=prod
LOGLEVEL=INFO
LOGCOUNT=30
ACCESSLOG=1

export path=/usr/local/bin:/usr/local/sbin:$PATH
PROJPATH=$(cd `dirname ${0}` && pwd)

cd "${PROJPATH}"

proc_id=0

#定义获取进程号方法
SANICARG="--env=$ENV --module $SANICMODULE --id=$proc_id"
pid=
pidnum=
function get_pid() {
    pid=`ps -ef | grep -E " $SANICARG " | grep -v "\<grep\>" | awk '{print $2}'`
    pidnum=`echo $pid | wc | awk '{print $2}'`
}

#定义停止服务方法
function stop_service() {
  get_pid
  if [ $pidnum -ne 0 ] ; then
    kill -15 $pid >/dev/null 2>&1
    if [ $? -ne 0 ] ; then
      echo "Cannot kill, stop ${SANICMODULE} failed."
      exit 1
    fi
    echo -n "${SANICMODULE} is stopping pid=$pid."
    while sleep 1
          get_pid
          [ $pidnum -ne 0 ]
    do
      echo -n "."
    done
    echo "stop done "
  else
    echo "${SANICMODULE} is not running, nothing to do!"
  fi
}

#定义开启服务方法
function start_service() {
  get_pid
  if [ $pidnum -ne 0 ] ; then
    echo "${SANICMODULE} is always runing, nothing done!"
  else
    ulimit -c 409600 >/dev/null 2>&1
    ulimit -n 65535 >/dev/null 2>&1
    mkdir -p "${PROJPATH}/logs"
    chmod +rw "${PROJPATH}/logs" -R >/dev/null 2>&1
    let "port=${BASEPORT}+${proc_id}"
    nohup python3 $PROJPATH/run_server.py $SANICARG $DEBUG $ACCESSLOG $LOGCOUNT \
      --host=$HOST \
      --port=$port \
      --loglevel=$LOGLEVEL \
      --logfile="logs/app.$ENV.$SANICMODULE.%d.log" \
      --workers=$WORKERS >> $PROJPATH/logs/console.$ENV.$SANICMODULE.$proc_id.log 2>&1 &
    echo -n "$SANICMODULE is starting"
    for ((i=0; i<3; i++)); do
      sleep 1
      get_pid
      if [ $pidnum -eq 0 ]; then
        echo -n "failed."
        exit 1
      fi
      echo -n "."
    done
    echo "start ok (pid=$pid)."
    exit 0
  fi
}

# 通过判断输入命令来进行相应的操作,目前支持三个命令
# start,stop和restart,可以通过传入不同的id来启动不同的进程
if [ $# -lt 1 ] ; then
  echo "usage: $0 [start|stop|restart] [id]"
  exit 1;
else
  if [ $# -ge 2 ]; then
    proc_id=$2
  fi
  if [ "$1" == 'stop' ] ; then
    stop_service
  elif [ "$1" == 'start' ]; then
    start_service
  elif [ "$1" == 'restart' ]; then
    stop_service
    start_service
  fi
fi

启动脚本也大致三个部分
1、定义参数
2、定义方法,如获取进程号,启动服务和停止服务
3、通过启动脚本命令运行不同的方法,达到启动和停止服务的效果

到此为止,封装也差不多了,赶紧来测试一下吧。

# main.py
from sanic import Sanic
from sanic.response import json

from utils.logUtil import logger

app = Sanic()

@app.route("/")
async def test(request):
    logger.info("hello world!")
    return json({"hello": "world"})

通过调用

./run_server.sh start

有没有发现服务已经启动了呢?

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

溪语流沙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值