private static void AugmentEntity(CommandAuditEntity entity, Provider provider, ICommand command, ICommandResult commandResult) { var timestamp = DateTime.UtcNow; entity.Provider = provider.Id; entity.Command = command.GetType().Name; entity.Project ??= command.ProjectId?.ToString(); entity.Created ??= timestamp; if (commandResult != null) { entity.Status = commandResult.RuntimeStatus; if (commandResult.RuntimeStatus.IsFinal()) { entity.Sent ??= timestamp; entity.Processed ??= timestamp; if (commandResult.RuntimeStatus == CommandRuntimeStatus.Failed) { // the command ran into an error - trace the error in our audit log entity.Errors = commandResult.Errors.Select(err => err.Message).ToArray(); } } else if (commandResult.RuntimeStatus.IsActive()) { // the provider returned an active state // so we could set the sent state and // tace the timeout returned by the provider entity.Sent ??= timestamp; entity.Timeout ??= timestamp + commandResult.Timeout; } } }
internal static string GetResultPath(this CommandAuditEntity commandAuditEntity) { if (commandAuditEntity is null) { throw new ArgumentNullException(nameof(commandAuditEntity)); } return($"{ToPathSegmentSafe(commandAuditEntity.OrganizationId)}/{ToPathSegmentSafe(commandAuditEntity.ProjectId)}/{commandAuditEntity.CommandId}.{RESULT_QUALIFIER}.json"); }
public static async Task RunActivity( [ActivityTrigger] IDurableActivityContext functionContext, [Table("AuditCommands")] CloudTable commandTable, ILogger log) { if (functionContext is null) { throw new ArgumentNullException(nameof(functionContext)); } if (commandTable is null) { throw new ArgumentNullException(nameof(commandTable)); } var(instanceId, provider, command, commandResult) = functionContext.GetInput <(string, Provider, ICommand, ICommandResult)>(); try { var entity = new CommandAuditEntity() { InstanceId = instanceId, CommandId = command.CommandId.ToString() }; var entityResult = await commandTable .ExecuteAsync(TableOperation.Retrieve <CommandAuditEntity>(entity.TableEntity.PartitionKey, entity.TableEntity.RowKey)) .ConfigureAwait(false); entity = entityResult.HttpStatusCode == (int)HttpStatusCode.OK ? (CommandAuditEntity)entityResult.Result : entity; AugmentEntity(entity, provider, command, commandResult); await commandTable .ExecuteAsync(TableOperation.InsertOrReplace(entity)) .ConfigureAwait(false); } catch (Exception exc) { log.LogWarning(exc, $"Failed to audit command {command?.GetType().Name ?? "UNKNOWN"} ({command?.CommandId ?? Guid.Empty}) for provider {provider?.Id ?? "UNKNOWN"}"); } }
private static async Task WriteAuditTableAsync(IBinder binder, string prefix, ICommand command, ICommandResult commandResult, string providerId) { var entity = new CommandAuditEntity(command, providerId); var auditTable = await binder .BindAsync <CloudTable>(new TableAttribute($"{prefix}Audit")) .ConfigureAwait(false); var entityResult = await auditTable .ExecuteAsync(TableOperation.Retrieve <CommandAuditEntity>(entity.TableEntity.PartitionKey, entity.TableEntity.RowKey)) .ConfigureAwait(false); if (entityResult.HttpStatusCode == (int)HttpStatusCode.OK) { entity = (entityResult.Result as CommandAuditEntity) ?? entity; } await auditTable .ExecuteAsync(TableOperation.InsertOrReplace(entity.Augment(command, commandResult))) .ConfigureAwait(false); }
public async Task <CommandAuditEntity> GetAsync(Guid organizationId, Guid commandId, bool includeJsonDumps = false) { var tableClient = await tableClientInstance .EnsureTableAsync() .ConfigureAwait(false); CommandAuditEntity entity = null; try { var response = await tableClient .GetEntityAsync <CommandAuditEntity>(organizationId.ToString(), commandId.ToString()) .ConfigureAwait(false); entity = response.Value; } catch (RequestFailedException ex) when(ex.Status == 404) { // doesn't exist return(null); } if (entity is not null && includeJsonDumps) { var blobContainerClient = await blobContainerClientInstance .EnsureContainerAsync() .ConfigureAwait(false); await Task.WhenAll( ReadBlobAsync(blobContainerClient, entity.GetCommandPath()) .ContinueWith(t => entity.CommandJson = t.Result, TaskContinuationOptions.OnlyOnRanToCompletion), ReadBlobAsync(blobContainerClient, entity.GetResultPath()) .ContinueWith(t => entity.ResultJson = t.Result, TaskContinuationOptions.OnlyOnRanToCompletion) ).ConfigureAwait(false); } return(entity);
private async Task WriteTableAsync(ICommand command, ICommandResult commandResult, string providerId) { var entity = new CommandAuditEntity(command, providerId); var auditTable = await GetAuditTableAsync() .ConfigureAwait(false); var entityResult = await auditTable .ExecuteAsync(TableOperation.Retrieve <CommandAuditEntity>(entity.PartitionKey, entity.RowKey)) .ConfigureAwait(false); if (entityResult.HttpStatusCode == (int)HttpStatusCode.OK) { entity = entityResult.Result as CommandAuditEntity ?? entity; } if (commandResult != null && !entity.RuntimeStatus.IsFinal()) { entity.Created = GetTableStorageMinDate(entity.Created, commandResult.CreatedTime); entity.Updated = GetTableStorageMaxDate(entity.Updated, commandResult.LastUpdatedTime); entity.RuntimeStatus = commandResult.RuntimeStatus; entity.CustomStatus = commandResult.CustomStatus ?? string.Empty; if (entity.RuntimeStatus.IsFinal()) { entity.Errors = string.Join(Environment.NewLine, commandResult.Errors.Select(error => $"[{error.Severity}] {error.Message}")); } if (!string.IsNullOrEmpty(entity.Provider)) { entity.Timeout ??= entity.Created?.Add(commandResult.Timeout); } } await auditTable .ExecuteAsync(TableOperation.InsertOrReplace(entity)) .ConfigureAwait(false);