// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Horde.Build.Server; using Horde.Build.Utilities; using HordeCommon; using Microsoft.Extensions.Logging; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; namespace Horde.Build.Logs { using LogId = ObjectId; /// /// Collection of event documents /// public class LogEventCollection : ILogEventCollection { class LogEventId { [BsonElement("l")] public LogId LogId { get; set; } [BsonElement("n")] public int LineIndex { get; set; } } class LogEventDocument : ILogEvent { [BsonId] public LogEventId Id { get; set; } [BsonElement("w"), BsonIgnoreIfDefault, BsonDefaultValue(false)] public bool IsWarning { get; set; } [BsonElement("c"), BsonIgnoreIfNull] public int? LineCount { get; set; } [BsonElement("s")] public ObjectId? SpanId { get; set; } LogId ILogEvent.LogId => Id.LogId; int ILogEvent.LineIndex => Id.LineIndex; int ILogEvent.LineCount => LineCount ?? 1; EventSeverity ILogEvent.Severity => IsWarning ? EventSeverity.Warning : EventSeverity.Error; public LogEventDocument() { Id = new LogEventId(); } public LogEventDocument(LogId logId, EventSeverity severity, int lineIndex, int lineCount, ObjectId? spanId) { Id = new LogEventId { LogId = logId, LineIndex = lineIndex }; IsWarning = severity == EventSeverity.Warning; LineCount = (lineCount > 1) ? (int?)lineCount : null; SpanId = spanId; } public LogEventDocument(NewLogEventData data) : this(data.LogId, data.Severity, data.LineIndex, data.LineCount, data.SpanId) { } } class LegacyEventDocument { public ObjectId Id { get; set; } public DateTime Time { get; set; } public EventSeverity Severity { get; set; } public LogId LogId { get; set; } public int LineIndex { get; set; } public int LineCount { get; set; } public string? Message { get; set; } [BsonIgnoreIfNull, BsonElement("IssueId2")] public int? IssueId { get; set; } public BsonDocument? Data { get; set; } public int UpgradeVersion { get; set; } } /// /// Collection of event documents /// readonly IMongoCollection _logEvents; /// /// Collection of legacy event documents /// readonly IMongoCollection _legacyEvents; /// /// Logger for upgrade messages /// readonly ILogger _logger; /// /// Constructor /// /// The database service instance /// The logger instance public LogEventCollection(MongoService mongoService, ILogger logger) { _logger = logger; List> logEventIndexes = new List>(); logEventIndexes.Add(keys => keys.Ascending(x => x.Id.LogId)); logEventIndexes.Add(keys => keys.Ascending(x => x.SpanId).Ascending(x => x.Id)); _logEvents = mongoService.GetCollection("LogEvents", logEventIndexes); _legacyEvents = mongoService.GetCollection("Events", keys => keys.Ascending(x => x.LogId)); } /// public Task AddAsync(NewLogEventData newEvent) { return _logEvents.InsertOneAsync(new LogEventDocument(newEvent)); } /// public Task AddManyAsync(List newEvents) { return _logEvents.InsertManyAsync(newEvents.ConvertAll(x => new LogEventDocument(x))); } /// public async Task> FindAsync(LogId logId, ObjectId? spanId = null, int? index = null, int? count = null) { _logger.LogInformation("Querying for log events for log {LogId} creation time {CreateTime}", logId, logId.Value.CreationTime); FilterDefinitionBuilder builder = Builders.Filter; FilterDefinition filter = builder.Eq(x => x.Id.LogId, logId); if(spanId != null) { filter &= builder.Eq(x => x.SpanId, spanId.Value); } IFindFluent results = _logEvents.Find(filter).SortBy(x => x.Id); if (index != null) { results = results.Skip(index.Value); } if (count != null) { results = results.Limit(count.Value); } List eventDocuments = await results.ToListAsync(); return eventDocuments.ConvertAll(x => x); } /// public async Task> FindEventsForSpansAsync(IEnumerable spanIds, LogId[]? logIds, int index, int count) { FilterDefinition filter = Builders.Filter.In(x => x.SpanId, spanIds.Select(x => x)); if (logIds != null && logIds.Length > 0) { filter &= Builders.Filter.In(x => x.Id.LogId, logIds); } List eventDocuments = await _logEvents.Find(filter).Skip(index).Limit(count).ToListAsync(); return eventDocuments.ConvertAll(x => x); } /// public async Task DeleteLogAsync(LogId logId) { await _logEvents.DeleteManyAsync(x => x.Id.LogId == logId); await _legacyEvents.DeleteManyAsync(x => x.LogId == logId); } /// public async Task AddSpanToEventsAsync(IEnumerable events, ObjectId spanId) { FilterDefinition eventFilter = Builders.Filter.In(x => x.Id, events.OfType().Select(x => x.Id)); UpdateDefinition eventUpdate = Builders.Update.Set(x => x.SpanId, spanId); await _logEvents.UpdateManyAsync(eventFilter, eventUpdate); } } }