Skynet日志服务

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启动画面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值