// Copyright Epic Games, Inc. All Rights Reserved. using System.IO.Pipelines; using System.Text; using Microsoft.Extensions.Logging; namespace HordeServer.Auditing { /// /// Message from an audit log /// public interface IAuditLogMessage { /// /// Timestamp for the event /// public DateTime TimeUtc { get; } /// /// Severity of the message /// public LogLevel Level { get; } /// /// The message payload. Should be an encoded JSON object, with format/properties fields. /// public string Data { get; } } /// /// Channel for a particular entity /// public interface IAuditLogChannel : ILogger { /// /// Finds messages matching certain criteria /// /// /// /// /// /// Cancellation token for the operation /// IAsyncEnumerable FindAsync(DateTime? minTime = null, DateTime? maxTime = null, int? index = null, int? count = null, CancellationToken cancellationToken = default); /// /// Deletes messages between a given time range for a particular object /// /// Minimum time to remove /// Maximum time to remove /// Cancellation token for the operation /// Async task Task DeleteAsync(DateTime? minTime = null, DateTime? maxTime = null, CancellationToken cancellationToken = default); /// /// Flush all writes to this log /// /// Cancellation token for the operation Task FlushAsync(CancellationToken cancellationToken = default); } /// /// Channel for a particular entity /// public interface IAuditLogChannel : IAuditLogChannel { /// /// Identifier for the subject of this channel /// TSubject Subject { get; } } /// /// Message in an audit log /// /// Type of entity that the log is for public interface IAuditLogMessage : IAuditLogMessage { /// /// Unique id for the entity /// public TTargetId Subject { get; } } /// /// Interface for a collection of log messages for a particular document type /// public interface IAuditLog { /// /// Get the channel for a particular subject /// /// /// IAuditLogChannel this[TSubject subject] { get; } } /// /// Factory for instantiating audit log instances /// /// public interface IAuditLogFactory { /// /// Create a new audit log instance, with the given database name /// /// /// /// IAuditLog Create(string collectionName, string subjectProperty); } /// /// Extension methods for audit log channels /// public static class AuditLogExtensions { /// /// Retrieve historical information about a specific agent /// /// Channel to query /// Writer for Json data /// Minimum time for records to return /// Maximum time for records to return /// Offset of the first result /// Number of records to return /// Cancellation token for the operation /// Information about the requested agent public static async Task FindAsync(this IAuditLogChannel channel, PipeWriter bodyWriter, DateTime? minTime = null, DateTime? maxTime = null, int index = 0, int count = 50, CancellationToken cancellationToken = default) { string prefix = "{\n\t\"entries\":\n\t["; await bodyWriter.WriteAsync(Encoding.UTF8.GetBytes(prefix), cancellationToken); string separator = ""; await foreach (IAuditLogMessage message in channel.FindAsync(minTime, maxTime, index, count, cancellationToken)) { string line = $"{separator}\n\t\t{message.Data}"; await bodyWriter.WriteAsync(Encoding.UTF8.GetBytes(line), cancellationToken); separator = ","; } string suffix = "\n\t]\n}"; await bodyWriter.WriteAsync(Encoding.UTF8.GetBytes(suffix), cancellationToken); } } }