上面说到是启动,下面接着说日志采集回到启动的地方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继续介绍数据发送的部分