github.com/TeaOSLab/EdgeNode@v1.3.8/internal/nodes/http_access_log_queue.go (about)

     1  package nodes
     2  
     3  import (
     4  	"bytes"
     5  	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
     6  	"github.com/TeaOSLab/EdgeNode/internal/goman"
     7  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
     8  	"github.com/TeaOSLab/EdgeNode/internal/rpc"
     9  	"github.com/TeaOSLab/EdgeNode/internal/utils"
    10  	memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
    11  	"google.golang.org/grpc/codes"
    12  	"google.golang.org/grpc/status"
    13  	"strings"
    14  	"time"
    15  	"unicode/utf8"
    16  )
    17  
    18  var sharedHTTPAccessLogQueue = NewHTTPAccessLogQueue()
    19  
    20  // HTTPAccessLogQueue HTTP访问日志队列
    21  type HTTPAccessLogQueue struct {
    22  	queue chan *pb.HTTPAccessLog
    23  
    24  	rpcClient *rpc.RPCClient
    25  }
    26  
    27  // NewHTTPAccessLogQueue 获取新对象
    28  func NewHTTPAccessLogQueue() *HTTPAccessLogQueue {
    29  	// 队列中最大的值,超出此数量的访问日志会被丢弃
    30  	var maxSize = 2_000 * (1 + memutils.SystemMemoryGB()/2)
    31  	if maxSize > 20_000 {
    32  		maxSize = 20_000
    33  	}
    34  
    35  	var queue = &HTTPAccessLogQueue{
    36  		queue: make(chan *pb.HTTPAccessLog, maxSize),
    37  	}
    38  	goman.New(func() {
    39  		queue.Start()
    40  	})
    41  
    42  	return queue
    43  }
    44  
    45  // Start 开始处理访问日志
    46  func (this *HTTPAccessLogQueue) Start() {
    47  	ticker := time.NewTicker(1 * time.Second)
    48  	for range ticker.C {
    49  		err := this.loop()
    50  		if err != nil {
    51  			if rpc.IsConnError(err) {
    52  				remotelogs.Debug("ACCESS_LOG_QUEUE", err.Error())
    53  			} else {
    54  				remotelogs.Error("ACCESS_LOG_QUEUE", err.Error())
    55  			}
    56  		}
    57  	}
    58  }
    59  
    60  // Push 加入新访问日志
    61  func (this *HTTPAccessLogQueue) Push(accessLog *pb.HTTPAccessLog) {
    62  	select {
    63  	case this.queue <- accessLog:
    64  	default:
    65  
    66  	}
    67  }
    68  
    69  // 上传访问日志
    70  func (this *HTTPAccessLogQueue) loop() error {
    71  	const maxLen = 2000
    72  	var accessLogs = make([]*pb.HTTPAccessLog, 0, maxLen)
    73  	var count = 0
    74  
    75  Loop:
    76  	for {
    77  		select {
    78  		case accessLog := <-this.queue:
    79  			accessLogs = append(accessLogs, accessLog)
    80  			count++
    81  
    82  			// 每次只提交 N 条访问日志,防止网络拥堵
    83  			if count >= maxLen {
    84  				break Loop
    85  			}
    86  		default:
    87  			break Loop
    88  		}
    89  	}
    90  
    91  	if len(accessLogs) == 0 {
    92  		return nil
    93  	}
    94  
    95  	// 发送到本地
    96  	if sharedHTTPAccessLogViewer.HasConns() {
    97  		for _, accessLog := range accessLogs {
    98  			sharedHTTPAccessLogViewer.Send(accessLog)
    99  		}
   100  	}
   101  
   102  	// 发送到API
   103  	if this.rpcClient == nil {
   104  		client, err := rpc.SharedRPC()
   105  		if err != nil {
   106  			return err
   107  		}
   108  		this.rpcClient = client
   109  	}
   110  
   111  	_, err := this.rpcClient.HTTPAccessLogRPC.CreateHTTPAccessLogs(this.rpcClient.Context(), &pb.CreateHTTPAccessLogsRequest{HttpAccessLogs: accessLogs})
   112  	if err != nil {
   113  		// 是否包含了invalid UTF-8
   114  		if strings.Contains(err.Error(), "string field contains invalid UTF-8") {
   115  			for _, accessLog := range accessLogs {
   116  				this.ToValidUTF8(accessLog)
   117  			}
   118  
   119  			// 重新提交
   120  			_, err = this.rpcClient.HTTPAccessLogRPC.CreateHTTPAccessLogs(this.rpcClient.Context(), &pb.CreateHTTPAccessLogsRequest{HttpAccessLogs: accessLogs})
   121  			return err
   122  		}
   123  
   124  		// 是否请求内容过大
   125  		statusCode, ok := status.FromError(err)
   126  		if ok && statusCode.Code() == codes.ResourceExhausted {
   127  			// 去除Body
   128  			for _, accessLog := range accessLogs {
   129  				accessLog.RequestBody = nil
   130  			}
   131  
   132  			// 重新提交
   133  			_, err = this.rpcClient.HTTPAccessLogRPC.CreateHTTPAccessLogs(this.rpcClient.Context(), &pb.CreateHTTPAccessLogsRequest{HttpAccessLogs: accessLogs})
   134  			return err
   135  		}
   136  
   137  		return err
   138  	}
   139  
   140  	return nil
   141  }
   142  
   143  // ToValidUTF8 处理访问日志中的非UTF-8字节
   144  func (this *HTTPAccessLogQueue) ToValidUTF8(accessLog *pb.HTTPAccessLog) {
   145  	accessLog.RemoteAddr = utils.ToValidUTF8string(accessLog.RemoteAddr)
   146  	accessLog.RemoteUser = utils.ToValidUTF8string(accessLog.RemoteUser)
   147  	accessLog.RequestURI = utils.ToValidUTF8string(accessLog.RequestURI)
   148  	accessLog.RequestPath = utils.ToValidUTF8string(accessLog.RequestPath)
   149  	accessLog.RequestFilename = utils.ToValidUTF8string(accessLog.RequestFilename)
   150  	accessLog.RequestBody = bytes.ToValidUTF8(accessLog.RequestBody, []byte{})
   151  	accessLog.Host = utils.ToValidUTF8string(accessLog.Host)
   152  	accessLog.Hostname = utils.ToValidUTF8string(accessLog.Hostname)
   153  
   154  	for k, v := range accessLog.SentHeader {
   155  		if !utf8.ValidString(k) {
   156  			delete(accessLog.SentHeader, k)
   157  			continue
   158  		}
   159  
   160  		for index, s := range v.Values {
   161  			v.Values[index] = utils.ToValidUTF8string(s)
   162  		}
   163  	}
   164  
   165  	accessLog.Referer = utils.ToValidUTF8string(accessLog.Referer)
   166  	accessLog.UserAgent = utils.ToValidUTF8string(accessLog.UserAgent)
   167  	accessLog.Request = utils.ToValidUTF8string(accessLog.Request)
   168  	accessLog.ContentType = utils.ToValidUTF8string(accessLog.ContentType)
   169  
   170  	for k, c := range accessLog.Cookie {
   171  		if !utf8.ValidString(k) {
   172  			delete(accessLog.Cookie, k)
   173  			continue
   174  		}
   175  		accessLog.Cookie[k] = utils.ToValidUTF8string(c)
   176  	}
   177  
   178  	accessLog.Args = utils.ToValidUTF8string(accessLog.Args)
   179  	accessLog.QueryString = utils.ToValidUTF8string(accessLog.QueryString)
   180  
   181  	for k, v := range accessLog.Header {
   182  		if !utf8.ValidString(k) {
   183  			delete(accessLog.Header, k)
   184  			continue
   185  		}
   186  		for index, s := range v.Values {
   187  			v.Values[index] = utils.ToValidUTF8string(s)
   188  		}
   189  	}
   190  
   191  	for k, v := range accessLog.Errors {
   192  		accessLog.Errors[k] = utils.ToValidUTF8string(v)
   193  	}
   194  }