restructure project
This commit is contained in:
		
							
								
								
									
										29
									
								
								pkg/mongo/bson_lookup.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								pkg/mongo/bson_lookup.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| package mongo | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"go.mongodb.org/mongo-driver/bson" | ||||
| ) | ||||
|  | ||||
| // default values without ok | ||||
|  | ||||
| func bsonLookupBoolean(b *bson.Raw, key ...string) bool { | ||||
| 	v, _ := b.Lookup(key...).BooleanOK() | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func bsonLookupStringValue(b *bson.Raw, key ...string) string { | ||||
| 	v, _ := b.Lookup(key...).StringValueOK() | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func bsonLookupInt64(b *bson.Raw, key ...string) int64 { | ||||
| 	v, _ := b.Lookup(key...).Int64OK() | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func bsonLookupTime(b *bson.Raw, key ...string) time.Time { | ||||
| 	v, _ := b.Lookup(key...).TimeOK() | ||||
| 	return v | ||||
| } | ||||
							
								
								
									
										60
									
								
								pkg/mongo/metrics.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								pkg/mongo/metrics.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| package mongo | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"log" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.k-space.ee/k-space/logmower-shipper/pkg/globals" | ||||
| 	prom "github.com/prometheus/client_golang/prometheus" | ||||
| 	"github.com/prometheus/client_golang/prometheus/promauto" | ||||
| 	mongoEvent "go.mongodb.org/mongo-driver/event" | ||||
| 	mongoOpt "go.mongodb.org/mongo-driver/mongo/options" | ||||
| ) | ||||
|  | ||||
| const promSubsystem = "database" | ||||
|  | ||||
| var ( | ||||
| 	promDbHeartbeat = promauto.NewHistogramVec(prom.HistogramOpts{ | ||||
| 		Namespace: globals.PrometheusPrefix, Subsystem: promSubsystem, | ||||
| 		Name:    "heartbeat_time", | ||||
| 		Help:    "Time in seconds for succeeded heartbeat, or 0 on failure", | ||||
| 		Buckets: []float64{0.1, 0.2, 0.5, 1, 5, 10, 50}, | ||||
| 	}, []string{"connection_id"}) | ||||
|  | ||||
| 	promDbCmd = promauto.NewHistogramVec(prom.HistogramOpts{ | ||||
| 		Namespace: globals.PrometheusPrefix, Subsystem: promSubsystem, | ||||
| 		Name:    "operation_latency", // "command_time", | ||||
| 		Help:    "Time in seconds of commands", | ||||
| 		Buckets: []float64{0.1, 0.2, 0.5, 1, 5, 10, 50}, | ||||
| 	}, []string{"connection_id", "command_name"}) | ||||
|  | ||||
| 	promDbCmdErr = promauto.NewCounterVec(prom.CounterOpts{ | ||||
| 		Namespace: globals.PrometheusPrefix, Subsystem: promSubsystem, | ||||
| 		Name: "errors", | ||||
| 		Help: "Failed commands (also reflected elsewhere)", | ||||
| 	}, []string{"connection_id", "command_name"}) | ||||
| ) | ||||
|  | ||||
| func monitoredClientOptions() *mongoOpt.ClientOptions { | ||||
| 	return mongoOpt.Client(). | ||||
| 		SetServerMonitor(&mongoEvent.ServerMonitor{ | ||||
| 			ServerHeartbeatSucceeded: func(ev *mongoEvent.ServerHeartbeatSucceededEvent) { | ||||
| 				promDbHeartbeat.WithLabelValues(ev.ConnectionID).Observe(time.Duration(ev.DurationNanos).Seconds()) | ||||
| 			}, | ||||
| 			ServerHeartbeatFailed: func(ev *mongoEvent.ServerHeartbeatFailedEvent) { | ||||
| 				promDbHeartbeat.WithLabelValues(ev.ConnectionID).Observe(0) | ||||
| 				log.Printf("database heartbeat failed on connection %q: %e", ev.ConnectionID, ev.Failure) | ||||
| 			}, | ||||
| 		}). | ||||
| 		SetMonitor(&mongoEvent.CommandMonitor{ | ||||
| 			Succeeded: func(_ context.Context, ev *mongoEvent.CommandSucceededEvent) { | ||||
| 				promDbCmd.WithLabelValues(ev.ConnectionID, ev.CommandName).Observe(time.Duration(ev.DurationNanos).Seconds()) | ||||
| 			}, | ||||
| 			Failed: func(_ context.Context, ev *mongoEvent.CommandFailedEvent) { | ||||
| 				promDbCmd.WithLabelValues(ev.ConnectionID, ev.CommandName).Observe(time.Duration(ev.DurationNanos).Seconds()) | ||||
|  | ||||
| 				promDbCmdErr.WithLabelValues(ev.ConnectionID, ev.CommandName).Add(1) | ||||
| 			}, | ||||
| 		}) | ||||
| } | ||||
							
								
								
									
										37
									
								
								pkg/mongo/mongo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								pkg/mongo/mongo.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| package mongo | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
|  | ||||
| 	"git.k-space.ee/k-space/logmower-shipper/pkg/globals" | ||||
| 	"go.mongodb.org/mongo-driver/mongo" | ||||
| ) | ||||
|  | ||||
| func Initialize(ctx context.Context, uri string) (*mongo.Collection, error) { | ||||
| 	uriParsed, err := url.ParseRequestURI(uri) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("parsing URI for database name: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	uriParsed.Path = uriParsed.Path[1:] // remove leading slash | ||||
| 	if uriParsed.Path == "" { | ||||
| 		return nil, fmt.Errorf("URI must include database name (as database to authenticate against)") | ||||
| 	} | ||||
|  | ||||
| 	dbOpt := monitoredClientOptions().ApplyURI(uri) | ||||
|  | ||||
| 	dbClient, err := mongo.Connect(globals.MongoTimeout(ctx)) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("connecting to %q: %w", dbOpt.GetURI(), err) | ||||
| 	} | ||||
|  | ||||
| 	col := dbClient.Database(uriParsed.Path).Collection("logs") | ||||
|  | ||||
| 	if err := InitializeIndexes(globals.MongoTimeout(ctx), col); err != nil { | ||||
| 		return nil, fmt.Errorf("initializing indexes: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	return col, nil | ||||
| } | ||||
							
								
								
									
										149
									
								
								pkg/mongo/mongo_struct.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								pkg/mongo/mongo_struct.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| package mongo | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"time" | ||||
|  | ||||
| 	"go.mongodb.org/mongo-driver/bson" | ||||
| 	"go.mongodb.org/mongo-driver/mongo" | ||||
| ) | ||||
|  | ||||
| // ctx is used directly | ||||
| func InitializeIndexes(ctx context.Context, col *mongo.Collection) error { | ||||
| 	ind := col.Indexes() | ||||
|  | ||||
| 	// (does not create duplicates) | ||||
| 	_, err := ind.CreateOne(ctx, mongo.IndexModel{ | ||||
| 		Keys: bson.D{{Key: RecordKeyFilePath, Value: 1}, {Key: RecordKeyOffset, Value: -1}}, | ||||
| 	}) | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // when editing, also edit everything in this file! | ||||
| type ( | ||||
| 	Record struct { | ||||
| 		*File | ||||
| 		Offset int64 // end, of last line | ||||
|  | ||||
| 		String string | ||||
| 		ParsedMetadata | ||||
|  | ||||
| 		ParsedContent // TODO: not implemented | ||||
|  | ||||
| 		// added by ToBson() | ||||
| 		timeShip time.Time | ||||
| 	} | ||||
| 	ParsedMetadata struct { | ||||
| 		TimeKubernetes time.Time | ||||
| 		StdErr         bool | ||||
| 	} | ||||
| 	ParsedContent struct { | ||||
| 		Content      any | ||||
| 		TimeUpstream time.Time | ||||
| 	} | ||||
| 	Host struct { | ||||
| 		Id   string | ||||
| 		Name string | ||||
| 		Arch string | ||||
| 	} | ||||
| 	File struct { | ||||
| 		Host *Host | ||||
| 		Path string // absolute | ||||
| 		KubeInfo | ||||
| 	} | ||||
| 	KubeInfo struct { | ||||
| 		ContainerName string | ||||
| 		ContainerId   string // unused | ||||
| 		Namespace     string | ||||
| 		Pod           string | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// used outside package for mongo commands | ||||
| 	RecordKeyHostId   = recordKeyHost + "." + recordKeyId | ||||
| 	RecordKeyFilePath = recordKeyLog + "." + recordKeyFile + "." + recordKeygenericPath | ||||
| 	RecordKeyOffset   = recordKeyLog + "." + recordKeyOffset | ||||
| ) | ||||
|  | ||||
| // Don't use direct strings in bson types. Use the constants as keys. | ||||
| // This ensures keys (and subkeys) are consistent within the package, and by consumers of it. | ||||
|  | ||||
| const ( | ||||
| 	recordKeygenericName = "name" | ||||
| 	recordKeygenericPath = "path" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	recordKeyString = "message" | ||||
|  | ||||
| 	recordKeyLog    = "log" | ||||
| 	recordKeyFile   = "file" | ||||
| 	recordKeyOffset = "offset" | ||||
| 	// recordKeyLevel  = "level" | ||||
|  | ||||
| 	recordKeyHost = "host" | ||||
| 	recordKeyId   = "id" | ||||
| 	recordKeyName = "name" | ||||
| 	recordKeyArch = "architecture" | ||||
|  | ||||
| 	recordKeyKubernetes = "kubernetes" | ||||
| 	recordKeyContainer  = "container" | ||||
| 	recordKeyNamespace  = "namespace" | ||||
| 	recordKeyPod        = "pod" | ||||
|  | ||||
| 	recordKeyStream = "stream" | ||||
|  | ||||
| 	recordKeyEvent          = "event" | ||||
| 	recordKeyTimeUpstream   = "created" | ||||
| 	recordKeyTimeKubernetes = "ingested" | ||||
|  | ||||
| 	recordKeyTimeMower = "@timestamp" | ||||
| ) | ||||
|  | ||||
| // not using marshal, since it is <0.1x performance | ||||
| func (l *Record) ToBson() bson.M { | ||||
| 	var stream string | ||||
| 	if l.StdErr { | ||||
| 		stream = "stderr" | ||||
| 	} else { | ||||
| 		stream = "stdout" | ||||
| 	} | ||||
|  | ||||
| 	return bson.M{ | ||||
| 		recordKeyString: l.String, | ||||
| 		recordKeyLog: bson.M{ | ||||
| 			recordKeyFile: bson.M{ | ||||
| 				recordKeygenericPath: l.File.Path, | ||||
| 			}, | ||||
| 			recordKeyOffset: l.Offset, | ||||
| 			// recordKeyLevel:  , //TODO: ECS | ||||
| 		}, | ||||
| 		recordKeyKubernetes: bson.M{ | ||||
| 			recordKeyContainer: bson.M{ | ||||
| 				recordKeygenericName: l.File.ContainerName, | ||||
| 			}, | ||||
| 			recordKeyNamespace: l.File.Namespace, | ||||
| 			recordKeyPod: bson.M{ | ||||
| 				recordKeygenericName: l.File.Pod, | ||||
| 			}, | ||||
| 		}, | ||||
| 		recordKeyHost: bson.M{ | ||||
| 			recordKeyId:   l.File.Host.Id, | ||||
| 			recordKeyName: l.File.Host.Name, | ||||
| 			recordKeyArch: l.File.Host.Arch, | ||||
| 		}, | ||||
| 		recordKeyStream: stream, | ||||
| 		recordKeyEvent: bson.M{ | ||||
| 			// recordKeyTimeUpstream:   l.TimeUpstream, //TODO: ECS | ||||
| 			recordKeyTimeKubernetes: l.TimeKubernetes, | ||||
| 		}, | ||||
|  | ||||
| 		recordKeyTimeMower: time.Now(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func RecordOffsetFromBson(b *bson.Raw) int64 { | ||||
| 	return bsonLookupInt64(b, recordKeyLog, recordKeyOffset) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user