背景

由于系统上会有很多服务,每个服务都会产生日志,如果每个服务都单独收集日志,那么会非常麻烦,所以需要一套轻量级的日志收集系统。

像elk这些日志收集系统我觉得都太笨重了,搭建起来很麻烦,所以选择loki作为日志收集系统,loki是一个开源的日志聚合系统,它使用了一种名为“日志流”的概念来处理日志数据,可以将日志数据存储在内存中,然后定期将数据持久化到磁盘上,loki的架构非常简单,只需要一个loki服务和一个grafana服务即可,loki服务负责接收日志数据,并将数据存储在内存中

安装

这里我们采用docker 安装,我觉得docker真的超级好用,不用去搭建过多的环境 docker-compose.yaml 加了version 会报过时这个错误,还没有去查看具体的原因,不加是没有什么问题的

这个yml 文件在官网也找的到,我只是做一个入门的介绍,具体可以参考官网

networks:
  loki:

services:
  loki:
    image: grafana/loki:2.9.2
    ports:
      - "3100:3100"
    command: -config.file=/etc/loki/local-config.yaml
    networks:
      - loki

  promtail:
    image: grafana/promtail:2.9.2
    volumes:
      - /var/log:/var/log
    command: -config.file=/etc/promtail/config.yml
    networks:
      - loki

  grafana:
    environment:
      - GF_PATHS_PROVISIONING=/etc/grafana/provisioning
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
    entrypoint:
      - sh
      - -euc
      - |
        mkdir -p /etc/grafana/provisioning/datasources
        cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
        apiVersion: 1
        datasources:
        - name: Loki
          type: loki
          access: proxy 
          orgId: 1
          url: http://loki:3100
          basicAuth: false
          isDefault: true
          version: 1
          editable: false
        EOF
        /run.sh        
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    networks:
      - loki

先给大家上个效果图

loki 内存占用 img.png

grafana 内存占用 img.png

promtail 内存占用 img.png

grafana 面板图 img.png

这样就能清晰的看我们的日志了

接入

go_zero 接入

幸好go_zero 的logx 有设置输入流的方法 logx.setWriter(writer)

现在我们需要实现一个writer,将日志输出到loki

package log

import (
	"github.com/afiskon/promtail-client/promtail"
	"github.com/zeromicro/go-zero/core/logx"
	"log"
	"os"
	"time"
)

var loki promtail.Client

func register(url, source_name, job_name string, sendLevel, PrintLevel promtail.LogLevel) promtail.Client {

	labels := "{source=\"" + source_name + "\",job=\"" + job_name + "\"}"
	conf := promtail.ClientConfig{
		PushURL:            url + "/api/prom/push",
		Labels:             labels,
		BatchWait:          5 * time.Second,
		BatchEntriesNumber: 10000,
		SendLevel:          sendLevel,
		PrintLevel:         PrintLevel,
	}

	loki, err := promtail.NewClientProto(conf)
	if err != nil {
		log.Printf("promtail.NewClient: %s\n", err)
		os.Exit(1)
	}
	loki.Infof("loki up sucessfully!")
	return loki
}

type LogWrite struct {
	// 注册loki
	loki promtail.Client
}

// NewLogWrite 创建日志写入器
// lokiUrl 地址
// sourceName 源名称
// jobName 任务名称
// sendLevel 发送级别
// PrintLevel 打印级别
func NewLogWrite(lokiUrl, sourceName, jobName string, sendLevel, PrintLevel promtail.LogLevel) *LogWrite {
	return &LogWrite{
		loki: register(lokiUrl, sourceName, jobName, sendLevel, PrintLevel),
	}
}

func (l *LogWrite) Alert(v any) {
	l.loki.Infof("err: %v", v)
}

func (l *LogWrite) Close() error {
	l.loki.Shutdown()
	return nil
}

func (l *LogWrite) Debug(v any, fields ...logx.LogField) {
	if len(fields) > 0 {
		l.loki.Debugf("err: %v, file:%s", v, fields[0].Value)
	} else {
		l.loki.Debugf("err: %v", v)
	}
}

func (l *LogWrite) Error(v any, fields ...logx.LogField) {

	if len(fields) > 0 {
		l.loki.Errorf("err: %v, file:%s", v, fields[0].Value)
	} else {
		l.loki.Errorf("err: %v", v)
	}
}

func (l *LogWrite) Info(v any, fields ...logx.LogField) {
	if len(fields) > 0 {
		l.loki.Infof("info: %v, file:%s", v, fields[0].Value)
	} else {
		l.loki.Infof("info: %v", v)
	}
}

func (l *LogWrite) Severe(v any) {
	l.loki.Errorf("err: %v", v)
}

func (l LogWrite) Slow(v any, fields ...logx.LogField) {
	if len(fields) > 0 {
		l.loki.Warnf("err: %v, file:%s", v, fields[0].Value)
	} else {
		l.loki.Warnf("err: %v", v)
	}
}

func (l *LogWrite) Stack(v any) {
	l.loki.Infof("err: %v", v)
}

func (l *LogWrite) Stat(v any, fields ...logx.LogField) {
	l.loki.Infof("err: %v", v)
}

img.png

一定要把setWriter 放在s.stop() 后面,不然输入流会关闭,无法生效