it is shipping and parsing
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,4 @@ | |||||||
| *.swp | *.swp | ||||||
| .vscode | .vscode | ||||||
| logs/ | logs/ | ||||||
|  | __debug_bin | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ import ( | |||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/signal" | 	"os/signal" | ||||||
| 	"path" | 	"path/filepath" | ||||||
| 	"runtime" | 	"runtime" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| @@ -116,6 +116,8 @@ var App = &cli.App{ | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			l.Fatal("parsing URI for mongo database name", zap.Error(err)) | 			l.Fatal("parsing URI for mongo database name", zap.Error(err)) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		uriParsed.Path = uriParsed.Path[1:] // remove leading slash | ||||||
| 		if uriParsed.Path == "" { | 		if uriParsed.Path == "" { | ||||||
| 			l.Fatal("mongo database name must be set in mongo URI") | 			l.Fatal("mongo database name must be set in mongo URI") | ||||||
| 		} | 		} | ||||||
| @@ -151,13 +153,12 @@ var App = &cli.App{ | |||||||
| 						continue | 						continue | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					absPath := path.Join(logDir, event.Name) |  | ||||||
| 					promFilesRead.Add(1) | 					promFilesRead.Add(1) | ||||||
| 					l.Debug("digesting new file", zap.String("name", absPath)) | 					l.Debug("digesting new file", zap.String("name", event.Name)) | ||||||
|  |  | ||||||
| 					wg.Add(1) | 					wg.Add(1) | ||||||
| 					go func() { | 					go func() { | ||||||
| 						state.shipFile(ctx.Context, absPath, ctx.Bool("delete-after-read")) | 						state.shipFile(ctx.Context, event.Name, ctx.Bool("delete-after-read")) | ||||||
| 						wg.Done() | 						wg.Done() | ||||||
| 					}() | 					}() | ||||||
|  |  | ||||||
| @@ -171,6 +172,8 @@ var App = &cli.App{ | |||||||
| 			} | 			} | ||||||
| 		}() | 		}() | ||||||
|  |  | ||||||
|  | 		// TODO: simulate create events files for current files | ||||||
|  |  | ||||||
| 		err = watcher.Add(logDir) | 		err = watcher.Add(logDir) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			promErrWatching.Add(1) | 			promErrWatching.Add(1) | ||||||
| @@ -222,3 +225,36 @@ func errAppend(a, b error) error { | |||||||
| 	} | 	} | ||||||
| 	return fmt.Errorf("%e; %e", a, b) | 	return fmt.Errorf("%e; %e", a, b) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type logMeta struct { | ||||||
|  | 	podName       string | ||||||
|  | 	podNamespace  string | ||||||
|  | 	containerName string | ||||||
|  | 	containerId   string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func parseLogName(name string) (m logMeta, ok bool) { | ||||||
|  | 	name = filepath.Base(name) | ||||||
|  |  | ||||||
|  | 	// https://github.com/kubernetes/design-proposals-archive/blob/8da1442ea29adccea40693357d04727127e045ed/node/kubelet-cri-logging.md | ||||||
|  | 	// <pod_name>_<pod_namespace>_<container_name>-<container_id>.log` | ||||||
|  |  | ||||||
|  | 	m.podName, name, ok = strings.Cut(name, "_") | ||||||
|  | 	if !ok { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	m.podNamespace, name, ok = strings.Cut(name, "_") | ||||||
|  | 	if !ok { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	m.containerName, name, ok = strings.Cut(name, "-") | ||||||
|  | 	if !ok { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	m.containerId = strings.TrimSuffix(name, ".log") | ||||||
|  |  | ||||||
|  | 	return m, true | ||||||
|  | } | ||||||
|   | |||||||
| @@ -28,6 +28,11 @@ var ( | |||||||
| 		Name:      "queue_dropped_count", | 		Name:      "queue_dropped_count", | ||||||
| 		Help:      "Items ready to be batched and sent to mongo, but dropped due to full queue", | 		Help:      "Items ready to be batched and sent to mongo, but dropped due to full queue", | ||||||
| 	}, []string{"filename"}) | 	}, []string{"filename"}) | ||||||
|  | 	promLineParsingErr = promauto.NewCounterVec(prom.CounterOpts{ | ||||||
|  | 		Subsystem: "shipper", | ||||||
|  | 		Name:      "lines_parsing_err_count", | ||||||
|  | 		Help:      "Errors while parsing log line suffixes", | ||||||
|  | 	}, []string{"filename"}) | ||||||
| 	promShipperQueueItems = promauto.NewHistogramVec(prom.HistogramOpts{ | 	promShipperQueueItems = promauto.NewHistogramVec(prom.HistogramOpts{ | ||||||
| 		Subsystem: "shipper", | 		Subsystem: "shipper", | ||||||
| 		Name:      "queue_items", | 		Name:      "queue_items", | ||||||
| @@ -137,20 +142,30 @@ func (s *submitter) senderRoutine(name string, batched <-chan []mLog, synced *bo | |||||||
|  |  | ||||||
| // when editing, also edit toBson(); all bson.D (and bson.M) uses | // when editing, also edit toBson(); all bson.D (and bson.M) uses | ||||||
| type mLog struct { | type mLog struct { | ||||||
| 	HostInfo HostInfo | 	HostInfo    HostInfo | ||||||
| 	Filename string | 	File        string | ||||||
| 	Offset   int64  // byte offset where log entry ends at | 	Offset      int64  // byte offset where log entry ends at | ||||||
| 	Content  string // TODO: | 	Content     string // TODO: | ||||||
| 	Time     time.Time | 	ShipTime    time.Time | ||||||
|  | 	CollectTime time.Time | ||||||
|  | 	StdErr      bool | ||||||
|  | 	Format      string // F or P TODO: what does it mean? Is there a well-defined log format for cri-o? | ||||||
| } | } | ||||||
|  |  | ||||||
| // not using marshal, since it is <0.1x performance | // not using marshal, since it is <0.1x performance | ||||||
| func (l *mLog) toBson() bson.M { | func (l *mLog) toBson() bson.M { | ||||||
| 	return bson.M{ | 	return bson.M{ | ||||||
| 		"host_info": l.HostInfo, | 		"host_info": bson.M{ | ||||||
| 		"filename":  l.Filename, | 			"id":   l.HostInfo.id, | ||||||
| 		"offset":    l.Offset, | 			"name": l.HostInfo.name, | ||||||
| 		"content":   l.Content, | 			"arch": l.HostInfo.arch, | ||||||
| 		"time":      l.Time, | 		}, | ||||||
|  | 		"filename":       l.File, | ||||||
|  | 		"offset":         l.Offset, | ||||||
|  | 		"content":        l.Content, | ||||||
|  | 		"ship_time":      l.ShipTime, | ||||||
|  | 		"container_time": l.CollectTime, | ||||||
|  | 		"stderr":         l.StdErr, | ||||||
|  | 		"format":         l.Format, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import ( | |||||||
| 	"io" | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| @@ -83,8 +84,9 @@ func (s *submitter) shipFile(ctx context.Context, name string, deleteAfterRead b | |||||||
| } | } | ||||||
|  |  | ||||||
| func (s *submitter) shipFileRoutine(ctx context.Context, name string, deleteOk func() bool, sendQueue chan<- mLog, sigCatchupped chan<- struct{}) error { | func (s *submitter) shipFileRoutine(ctx context.Context, name string, deleteOk func() bool, sendQueue chan<- mLog, sigCatchupped chan<- struct{}) error { | ||||||
| 	// TODO: better way for respecting ?killing sender for retry | 	baseName := filepath.Base(name) | ||||||
|  |  | ||||||
|  | 	// TODO: better way for respecting ?killing sender for retry | ||||||
| 	for { | 	for { | ||||||
| 		if len(sendQueue) == 0 { | 		if len(sendQueue) == 0 { | ||||||
| 			break | 			break | ||||||
| @@ -92,34 +94,31 @@ func (s *submitter) shipFileRoutine(ctx context.Context, name string, deleteOk f | |||||||
| 		time.Sleep(time.Second) | 		time.Sleep(time.Second) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Initialize in case of new file |  | ||||||
| 	log := mLog{ |  | ||||||
| 		HostInfo: s.hostInfo, |  | ||||||
| 		Filename: name, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// get files with offset | 	// get files with offset | ||||||
| 	offsetResult, err := mongoWithErr(s.db.FindOne(mongoTimeoutCtx(ctx), | 	offsetResult, err := mongoWithErr(s.db.FindOne(mongoTimeoutCtx(ctx), | ||||||
| 		bson.D{{Key: "hostinfo.id", Value: s.hostInfo.id}, {Key: "filename", Value: name}}, | 		bson.D{{Key: "hostinfo.id", Value: s.hostInfo.id}, {Key: "file", Value: baseName}}, | ||||||
| 		&mongoOpt.FindOneOptions{Sort: bson.D{{Key: "offset", Value: -1}}}, // sort descending (get largest) | 		&mongoOpt.FindOneOptions{Sort: bson.D{{Key: "offset", Value: -1}}}, // sort descending (get largest) | ||||||
| 	)) | 	)) | ||||||
|  |  | ||||||
| 	if err != nil && !errors.Is(err, mongo.ErrNoDocuments) { | 	if err != nil && !errors.Is(err, mongo.ErrNoDocuments) { | ||||||
| 		return fmt.Errorf("retrieving mongo offset: %w", err) | 		return fmt.Errorf("retrieving mongo offset: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// offsetResult.DecodeBytes() //TODO: check for extra fields | 	// offsetResult.DecodeBytes() //TODO: check for extra fields | ||||||
|  |  | ||||||
|  | 	var log mLog | ||||||
| 	if err := offsetResult.Decode(&log); err != nil && !errors.Is(err, mongo.ErrNoDocuments) { | 	if err := offsetResult.Decode(&log); err != nil && !errors.Is(err, mongo.ErrNoDocuments) { | ||||||
| 		return fmt.Errorf("decoding mongo offset: %w", err) | 		return fmt.Errorf("decoding mongo offset: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	fi, err := os.Stat(log.Filename) | 	fi, err := os.Stat(name) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("getting original file size") | 		return fmt.Errorf("getting original file size") | ||||||
| 	} | 	} | ||||||
| 	startSize := fi.Size() | 	startSize := fi.Size() | ||||||
|  |  | ||||||
| 	// TODO: use inotify for file, and end with file deletion or replacement | 	// TODO: use inotify for file, and end with file deletion or replacement | ||||||
| 	lineChan, errChan, err := util.TailFile(ctx, log.Filename, log.Offset, io.SeekStart) | 	lineChan, errChan, err := util.TailFile(ctx, name, log.Offset, io.SeekStart) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("tailing file: %w", err) | 		return fmt.Errorf("tailing file: %w", err) | ||||||
| 	} | 	} | ||||||
| @@ -137,22 +136,47 @@ func (s *submitter) shipFileRoutine(ctx context.Context, name string, deleteOk f | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			var collectTime time.Time | ||||||
|  | 			var stdErr, format, log string | ||||||
|  |  | ||||||
|  | 			split := strings.SplitN(line.String, " ", 4) | ||||||
|  | 			if len(split) != 4 { | ||||||
|  | 				log = line.String | ||||||
|  | 				promLineParsingErr.WithLabelValues(baseName).Add(1) | ||||||
|  |  | ||||||
|  | 			} else { | ||||||
|  | 				stdErr, format, log = split[1], split[2], split[3] | ||||||
|  |  | ||||||
|  | 				collectTime, err = time.Parse(time.RFC3339Nano, split[0]) | ||||||
|  | 				if err != nil { | ||||||
|  | 					promLineParsingErr.WithLabelValues(baseName).Add(1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			select { | 			select { | ||||||
| 			case sendQueue <- mLog{ | 			case sendQueue <- mLog{ | ||||||
| 				HostInfo: s.hostInfo, | 				HostInfo: s.hostInfo, | ||||||
| 				Filename: *line.Filename, | 				File:     baseName, | ||||||
|  |  | ||||||
| 				Offset:   line.EndOffset, | 				Offset:   line.EndOffset, | ||||||
| 				Content:  line.String, | 				ShipTime: time.Now(), | ||||||
|  |  | ||||||
|  | 				CollectTime: collectTime, | ||||||
|  | 				StdErr:      stdErr == "stderr", // or stdout | ||||||
|  | 				Format:      format, | ||||||
|  | 				Content:     log, | ||||||
| 			}: | 			}: | ||||||
|  |  | ||||||
| 			default: | 			default: | ||||||
| 				promShipperDropped.WithLabelValues(*line.Filename).Add(1) | 				promShipperDropped.WithLabelValues(baseName).Add(1) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// no new lines | 			// no new lines | ||||||
| 		default: | 			// TODO: ensure we don't instantly jump here | ||||||
| 			if deleteOk() { | 			// default: | ||||||
| 				return os.Remove(name) | 			// 	if deleteOk() { | ||||||
| 			} | 			// 		return os.Remove(name) | ||||||
|  | 			// 	} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user