skynet log

作者: JunChow520 | 来源:发表于2019-03-03 23:08 被阅读0次

    Skynet的日志输出是由service_src/service_logger.c文件实现的,用于运行Lua脚本的snlua服务等,编译成.so库供Skynet框架使用。

    日志输出服务工作原理

    当Skynet启动的时会根据配置文件指定的日志文件创建一个logger context日志上下文,具体过程:先找到logger.so动态链接库文件,而后调用其logger_create函数构建日志服务对应的context上下文。重要的是,在构建context上下文对象时,会注册该服务的回调函_logger()和消息队列,最后执行logger_init函数将logger的消息队列放入global queue全局队列中去。

    日志输出服务工作方式

    在Skynet启动时会启动一个logger服务,默认是logger类型服务,也可以配置为snlua类型。

    // skynet/skynet-src/skynet_start.c
    struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger);
    

    可以配置logger服务类型,默认为logger

    //skynet/example/config
    daemon = "./skynet.pid" // 以后台模式启动Skynet
    logger=  "./skynet.log" //配置后台模式下日志输出的目录文件
    

    当在后台模式下启动时,日志便输出到当前目录下的skynet.log文件中,这种做法的缺点是当日志文件过大时Skynet不会自动删除,因此对系统影响很大。现在需要按天数保存日志,使超过1周的日志自动删除掉。另外一个问题是,如果将skynet.log文件cp复制之后删除在重建的话,日志是不会打印在这个文件中的,需要重启Skynet服务器才会生效。

    //skynet/skynet-src/skynet_main.c
    config.logger = optstring("logger", NULL);
    config.logservice = optstring("logservice", "logger");
    

    启动一个logger日志输出服务时会调用到logger_create这个API,用于申请struct logger结构,最后赋值给ctx->instance

    //skynet/service-src/service_logger.c
    struct logger
    {
      FILE * handle;//文件句柄
      char * filename;//文件名称
      int close; //0表示文件句柄是标准输出,1表示输出到文件
    }
    
    struct logger *
    logger_create(void)
    {
      struct logger * inst = skynet_malloc(sizeof(*inst));
      inst->handle = NULL;
      inst->close = 0;
      inst->filename = NULL;
    
      return inst;
    }
    

    logger服务退出时,会调用logger_release函数这个API进行清理回收。

    //skynet/service-src/logger_service.c
    void 
    logger_release(struct logger * inst)
    {
      if(inst->close)
      {
        fclose(inst->handle);
      }
      skynet_free(inst->filename);
      skynet_free(inst);
    }
    

    之后会调用logger_init函数API,参数param是配置中的config->logger。若指定文件路径则打开该文件inst->handle = fopen(param, "w")。否则,设置文件句柄为标准输出inst->handle = stdout。最后设置ctx上下文的消息回调函数为logger_cb(skynet_callback),并给ctx注册一个名称为skynet_command(ctx, "REG", "logger")

    //skynet/servivce-src/service_logger.c
    int logger_init(struct logger * inst, struct skynet_context *ctx, const char * param)
    {
      if(param)
      {
        inst->handle = fopen(param, "w");
        if(inst->handle == NULL)
        {
          return 1;//打开失败
        }
        inst->filename = skynet_malloc(strlen(param) + 1);
        strcpy(inst->filename, param);
      }
      else
      {
        // 使用标准输出
        inst->handle = stdout;
      }
    
      if(inst->handle)
      {
        //注册回调函数,用来处理消息的
        skynet_callback(ctx, inst, logger_cb);
        //注册命令的局部名字
        skynet_command(ctx, "REG", ".logger");
        return 0;
      }
      return 1;
    }
    

    Skynet输出日志通常是调用skynet_error函数API,Lua层调用skynet.error最后也是调用skynet_error函数。查找名为logger对应的ctxhandle,然后向该id发送消息包skynet_context_push,消息包的类型为PTYPE_TEXT,若没有设置PTYPE_ALLOCSESSION标记则表示不需要接收方返回。

    //skynet/skynet-src/skynet_error.c
    void skynet_error(struct skynet_context * context, const char msg, ... )
    {
      static uint32_t logger = 0;
      if(logger == 0)
      {
        logger = skynet_handle_findname("logger");
      }
      ...
      //将错误消息字符串构建为skynet_message并放入logger消息队列
      struct skynet_message smsg;
      if(context == NULL)
      {
        smsg.source = 0;
      }
      else
      {
        //source是ctx的hangle编号
        smsg.source = skynet_context_handle(context);
      }
      smsg.session = 0;
      smsg.data = data;
      //sz 表示低24位保存的是数据大小
      smsg.sz = len | ((size_t)PTYPE_TEXT << MESSAGE_TYPE_SHIFT);
      // 将消息放到logger的队列中
      skynet_context_push(logger, &smsg);
    }
    

    代码中错误处理的skynet_error()函数就是日志输出函数,它会将错误字符串构建为一个skynet_message,并放入到logger的消息队列中,msg中会保存消息来来源handle

    那么消息何时输出呢?Skynet启动时会创建 worker 工作线程,它的工作就是消费这些消息并执行对应的回调函数,对于logger而言,相当于把消息输出到对应的文件句柄中。

    static void
    _dispatch_message(struct skynet_context *ctx, struct skynet_message *msg)
    

    当工作线程分发这条消息包时,最终会调用logger服务的消息回调函数logger_cb,不同服务类型的消息回调函数接口参数是一样的。

    //skynet/service-src/logger_service.c
    static int logger_cb(struct skynet_context * context, void *ud, int type, int session, uint32_t source, const void * msg, size_t sz)
    {
      struct logger * inst = ud;
      switch(type)
      { 
        case PTYPE_SYSTEM:
          if(inst->filename)
          {
            inst->handle = freopen(inst->filename, "a", inst->handle);
          }
          break;
        case PTYPE_TEXT:
          fprintf(inst->handle, "[:%08x]", source);
          fwrite(msg, sz, 1, inst->handle);
          fprintf(inst->handle, "\n");
          fflush(inst->handle);
          break;
      }
      return 0;
    }
    

    由于skynet_error发送的消息包的typePTYPE_TEXT,它会将小细胞源地址以及消息包数据一起写到文件句柄中。在skynet_context_new成功启动一个服务后,会调用skynet_error(ctx, "LAUNCH %s %s", name, param ? param:""),于是就有了经典的Skynet启动画面。

    相关文章

      网友评论

        本文标题:skynet log

        本文链接:https://www.haomeiwen.com/subject/prwmuqtx.html