filebeat源码分析之采集

上面说到是启动,下面接着说日志采集回到启动的地方filebeat/prospector/prospector.go。

func (p *Prospector) Run() {
    // 初始化 prospector 启动
    p.prospectorer.Run()

    // 如果只运行一次就返回
    if p.Once {
        return
    }

    for {
        select {
        case <-p.done:
            logp.Info("Prospector ticker stopped")
            return
        case <-time.After(p.config.ScanFrequency):
            logp.Debug("prospector", "Run prospector")
            p.prospectorer.Run()
        }
    }
}

这个是启动采集的地方,下面详细的分析一下。首先是第一次启动的时候,它是遍历需要采集的管理的日志,然后为每个文件创建采集harvester。然后就每隔p.config.ScanFrequency(默认10s)时间发起一次日志采集p.prospectorer.Run()。关键问题都在这个Run方法里面是吧,filebeat/prospector/log/prospector.go这里已常用的log为例,

p.scan()

看看他是如何扫描的

func (p *Prospector) scan() {
    var sortInfos []FileSortInfo
    var files []string

    paths := p.getFiles()
    ....
}

通过getFiles()这个方法。

func (p *Prospector) getFiles() map[string]os.FileInfo {
    paths := map[string]os.FileInfo{}

    for _, path := range p.config.Paths {
        matches, err := filepath.Glob(path)
        if err != nil {
            logp.Err("glob(%s) failed: %v", path, err)
            continue
        }

    OUTER:
        // 上面是获取符合的文件,下面是逐一条件判断是否需要采集
        for _, file := range matches {

            //这里是去除需要排除的文件
            if p.isFileExcluded(file) {
                logp.Debug("prospector", "Exclude file: %s", file)
                continue
            }

            //去除软连接文件,去除目录
            fileInfo, err := os.Lstat(file)
            if err != nil {
                logp.Debug("prospector", "lstat(%s) failed: %s", file, err)
                continue
            }

            if fileInfo.IsDir() {
                logp.Debug("prospector", "Skipping directory: %s", file)
                continue
            }

            isSymlink := fileInfo.Mode()&os.ModeSymlink > 0
            if isSymlink && !p.config.Symlinks {
                logp.Debug("prospector", "File %s skipped as it is a symlink.", file)
                continue
            }

            fileInfo, err = os.Stat(file)
            if err != nil {
                logp.Debug("prospector", "stat(%s) failed: %s", file, err)
                continue
            }

            // 如果软连接是允许的,确保不会重复采集
            if p.config.Symlinks {
                for _, finfo := range paths {
                    if os.SameFile(finfo, fileInfo) {
                        logp.Info("Same file found as symlink and originap. Skipping file: %s", file)
                        continue OUTER
                    }
                }
            }

            paths[file] = fileInfo
        }
    }

    return paths
}

这个就确定了需要采集的文件。
然后问每个文件创建采集器。主要创建的地方在

for i := 0; i < len(paths); i++ {
...
if lastState.IsEmpty() {
            logp.Debug("prospector", "Start harvester for new file: %s", newState.Source)
            err := p.startHarvester(newState, 0)
if err != nil {
                logp.Err("Harvester could not be started on new file: %s, Err: %s", newState.Source, err)
            }
} else {
            p.harvestExistingFile(newState, lastState)
}
...
}

通过for循环遍历每个path。这里的lastState是上一个状态,newState是当前状态。启动startHarvester。filebeat/prospector/log/prospector.go

func (p *Prospector) startHarvester(state file.State, offset int64) error {
    if p.numHarvesters.Inc() > p.config.HarvesterLimit && p.config.HarvesterLimit > 0 {
        p.numHarvesters.Dec()
        harvesterSkipped.Add(1)
        return fmt.Errorf("Harvester limit reached")
    }

    state.Finished = false
    state.Offset = offset

    // 创建Harvester
    h, err := p.createHarvester(state, func() { p.numHarvesters.Dec() })
    if err != nil {
        p.numHarvesters.Dec()
        return err
    }

    err = h.Setup()
    if err != nil {
        p.numHarvesters.Dec()
        return fmt.Errorf("Error setting up harvester: %s", err)
    }

    // 在启动之前先更新状态,
    h.SendStateUpdate()

    if err = p.harvesters.Start(h); err != nil {
        p.numHarvesters.Dec()
    }
    return err
}

上面的先是创建

func (p *Prospector) createHarvester(state file.State, onTerminate func()) (*Harvester, error) {
    // 这里的outlet是发送数据用的,后面会用到SubOutlet
    outlet := channel.SubOutlet(p.outlet)
    h, err := NewHarvester(
        p.cfg,
        state,
        p.states,
        func(d *util.Data) bool {
            return p.stateOutlet.OnEvent(d)
        },
        outlet,
    )
    h.onTerminate = onTerminate
    return h, err
}

然后启动Start。filebeat/prospector/log/harvester.go

message, err := h.reader.Next()
state.Offset += int64(message.Bytes)
...
if !h.sendEvent(data) {
    return nil
}

显示读取,然后是更新offset的值。发送数据,

func (h *Harvester) sendEvent(data *util.Data) bool {
    if h.source.HasState() {
        h.states.Update(data.GetState())
    }

    err := h.forwarder.Send(data)
    return err == nil
}

这里通过Send方法发送数据,filebeat/harvester/forwarder.go

func (f *Forwarder) Send(data *util.Data) error {
    ok := f.Outlet.OnEvent(data)
    if !ok {
        logp.Info("Prospector outlet closed")
        return errors.New("prospector outlet closed")
    }

    return nil
}

上面代码是将数据发送到spooler,这个spooler是发送数据的一个池子。Outlet就是上面说的SubOutlet。

func (o *subOutlet) OnEvent(d *util.Data) bool {
    if !o.isOpen.Load() {
        return false
    }

    select {
    case <-o.done:
        close(o.ch)
        return false

    case o.ch <- d:
        select {
        case <-o.done:
        //这里当数据发送到publisher pipeline后就可以关闭这个chan了。
            close(o.ch)
            return true
        case ret := <-o.res:
            return ret
        }
    }
}

下面就看到发到的地方filebeat/channel/outlet.go

    o.client.Publish(event)

后面的blog继续介绍数据发送的部分

©️2022 CSDN 皮肤主题:Age of Ai 设计师:meimeiellie 返回首页
评论

打赏作者

柳清风09

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

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值