Skip to content

Commit

Permalink
Merge pull request #1834 from qdraw/feature/202411_notification_error
Browse files Browse the repository at this point in the history
Feature/202411 notification error
  • Loading branch information
qdraw authored Nov 16, 2024
2 parents 39dcf68 + 80f2b29 commit bea959e
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 253 deletions.
3 changes: 2 additions & 1 deletion history.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ Semantic Versioning 2.0.0 is from version 0.1.6+
## List of versions

## version 0.6.4 - _(Unreleased)_ - 2024-11-?? {#v0.6.3}
- nothing yet

- [x] (Fixed) _Back-end_ Notification duplicate error handling (Issue #1832) (PR #1834)

## version 0.6.3 - 2024-11-14 {#v0.6.3}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ await timer.WaitForNextTickAsync(cancellationToken) )
catch ( OperationCanceledException exception )
{
_logger.LogError(
$"[StartBackgroundAsync] catch-ed OperationCanceledException {exception.Message}",
$"[StartBackgroundAsync] catch-ed OperationCanceledException " +
$"Src:{exception.Source} Mes:{exception.Message} SaTtr:{exception.StackTrace}" +
$" Inner:{exception.InnerException?.StackTrace} HRes:{exception.HResult}",
exception);
}

Expand Down
161 changes: 96 additions & 65 deletions starsky/starsky.foundation.database/Notifications/NotificationQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using starsky.foundation.database.Data;
Expand All @@ -15,49 +16,87 @@
using starsky.foundation.platform.JsonConverter;
using starsky.foundation.platform.Models;

namespace starsky.foundation.database.Notifications
namespace starsky.foundation.database.Notifications;

[Service(typeof(INotificationQuery), InjectionLifetime = InjectionLifetime.Scoped)]
public sealed class NotificationQuery : INotificationQuery
{
[Service(typeof(INotificationQuery), InjectionLifetime = InjectionLifetime.Scoped)]
public sealed class NotificationQuery : INotificationQuery
private readonly ApplicationDbContext _context;
private readonly IWebLogger _logger;
private readonly IServiceScopeFactory _scopeFactory;

public NotificationQuery(ApplicationDbContext context, IWebLogger logger,
IServiceScopeFactory scopeFactory)
{
private readonly ApplicationDbContext _context;
private readonly IWebLogger _logger;
private readonly IServiceScopeFactory _scopeFactory;
_context = context;
_logger = logger;
_scopeFactory = scopeFactory;
}

public NotificationQuery(ApplicationDbContext context, IWebLogger logger,
IServiceScopeFactory scopeFactory)
{
_context = context;
_logger = logger;
_scopeFactory = scopeFactory;
}
public Task<List<NotificationItem>> GetNewerThan(DateTime parsedDateTime)
{
var unixTime = ( ( DateTimeOffset ) parsedDateTime ).ToUnixTimeSeconds() - 1;
return _context.Notifications.Where(x => x.DateTimeEpoch > unixTime).ToListAsync();
}

public async Task<NotificationItem> AddNotification(string content)
public Task<List<NotificationItem>> GetOlderThan(DateTime parsedDateTime)
{
var unixTime = ( ( DateTimeOffset ) parsedDateTime ).ToUnixTimeSeconds();
return _context.Notifications.Where(x => x.DateTimeEpoch < unixTime).ToListAsync();
}

public async Task RemoveAsync(IEnumerable<NotificationItem> content)
{
_context.Notifications.RemoveRange(content);
await _context.SaveChangesAsync();
}

/// <summary>
/// Add notification to the database
/// </summary>
/// <param name="content">Content</param>
/// <typeparam name="T">Type</typeparam>
/// <returns>DatabaseItem</returns>
public Task<NotificationItem> AddNotification<T>(ApiNotificationResponseModel<T> content)
{
var stringMessage = JsonSerializer.Serialize(content,
DefaultJsonSerializer.CamelCaseNoEnters);
return AddNotification(stringMessage);
}

/// <summary>
/// Add content to database
/// </summary>
/// <param name="content">json content</param>
/// <returns>item with id</returns>
public async Task<NotificationItem> AddNotification(string content)
{
var item = new NotificationItem
{
var item = new NotificationItem
{
DateTime = DateTime.UtcNow,
DateTimeEpoch = DateTimeOffset.Now.ToUnixTimeSeconds(),
Content = content
};
DateTime = DateTime.UtcNow,
DateTimeEpoch = DateTimeOffset.Now.ToUnixTimeSeconds(),
Content = content
};

return await RetryHelper.DoAsync(LocalAddQuery, TimeSpan.FromSeconds(1));
return await RetryHelper.DoAsync(LocalAddQuery, TimeSpan.FromSeconds(1));

async Task<NotificationItem> LocalAdd(ApplicationDbContext context)
async Task<NotificationItem> LocalAdd(ApplicationDbContext context)
{
try
{
try
{
context.Entry(item).State = EntityState.Added;
await context.Notifications.AddAsync(item);
await context.SaveChangesAsync();
return item;
}
catch ( DbUpdateConcurrencyException concurrencyException )
context.Entry(item).State = EntityState.Added;
await context.Notifications.AddAsync(item);
await context.SaveChangesAsync();
return item;
}
catch ( DbUpdateException updateException )
{
if ( updateException is DbUpdateConcurrencyException )
{
_logger.LogInformation(
"[AddNotification] try to fix DbUpdateConcurrencyException",
concurrencyException);
SolveConcurrency.SolveConcurrencyExceptionLoop(concurrencyException.Entries);
updateException);
SolveConcurrency.SolveConcurrencyExceptionLoop(updateException.Entries);
try
{
await _context.SaveChangesAsync();
Expand All @@ -68,48 +107,40 @@ async Task<NotificationItem> LocalAdd(ApplicationDbContext context)
"[AddNotification] save failed after DbUpdateConcurrencyException");
}
}

return item;
}

async Task<NotificationItem> LocalAddQuery()
{
try
else if ( updateException.InnerException is SqliteException
{
SqliteErrorCode: 19
} )
{
return await LocalAdd(_context);
_logger.LogInformation($"[AddNotification] SqliteException retry next: " +
$"{updateException.InnerException.Message}");

item.Id = 0;
await LocalAddQuery();
}
catch ( ObjectDisposedException )
else
{
// Include create new scope factory
var context = new InjectServiceScope(_scopeFactory).Context();
return await LocalAdd(context);
_logger.LogError(updateException,
$"[AddNotification] no solution maybe retry? M: {updateException.Message}");
throw;
}
}
}

public Task<NotificationItem> AddNotification<T>(ApiNotificationResponseModel<T> content)
{
var stringMessage = JsonSerializer.Serialize(content,
DefaultJsonSerializer.CamelCaseNoEnters);
return AddNotification(stringMessage);
return item;
}

public Task<List<NotificationItem>> GetNewerThan(DateTime parsedDateTime)
async Task<NotificationItem> LocalAddQuery()
{
var unixTime = ( ( DateTimeOffset ) parsedDateTime ).ToUnixTimeSeconds() - 1;
return _context.Notifications.Where(x => x.DateTimeEpoch > unixTime).ToListAsync();
}

public Task<List<NotificationItem>> GetOlderThan(DateTime parsedDateTime)
{
var unixTime = ( ( DateTimeOffset ) parsedDateTime ).ToUnixTimeSeconds();
return _context.Notifications.Where(x => x.DateTimeEpoch < unixTime).ToListAsync();
}

public async Task RemoveAsync(IEnumerable<NotificationItem> content)
{
_context.Notifications.RemoveRange(content);
await _context.SaveChangesAsync();
try
{
return await LocalAdd(_context);
}
catch ( ObjectDisposedException )
{
// Include create new scope factory
var context = new InjectServiceScope(_scopeFactory).Context();
return await LocalAdd(context);
}
}
}
}
65 changes: 35 additions & 30 deletions starsky/starsky.foundation.webtelemetry/Helpers/SetupLogging.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand All @@ -9,40 +10,44 @@
using starsky.foundation.platform.Models;
using starsky.foundation.platform.Services;

namespace starsky.foundation.webtelemetry.Helpers
namespace starsky.foundation.webtelemetry.Helpers;

public static class SetupLogging
{
public static class SetupLogging
private const string HostNameKey = "host.name";
private static readonly KeyValuePair<string, object> HostNameKeyValue = new(HostNameKey,
Environment.MachineName);

[SuppressMessage("Usage", "S4792:Make sure that this logger's configuration is safe.")]
public static void AddTelemetryLogging(this IServiceCollection services,
AppSettings appSettings)
{
[SuppressMessage("Usage", "S4792:Make sure that this logger's configuration is safe.")]
public static void AddTelemetryLogging(this IServiceCollection services,
AppSettings appSettings)
services.AddLogging(logging =>
{
services.AddLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
logging.ClearProviders();
logging.AddConsole();

if ( !string.IsNullOrEmpty(appSettings.OpenTelemetry?.LogsEndpoint) )
{
logging.AddOpenTelemetry(
builder =>
builder.AddOtlpExporter(
options =>
{
options.Protocol = OtlpExportProtocol.HttpProtobuf;
options.Headers = appSettings.OpenTelemetry.GetLogsHeader();
options.Endpoint =
new Uri(appSettings.OpenTelemetry.LogsEndpoint);
})
.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService(appSettings.OpenTelemetry.GetServiceName())
)
);
}
});
if ( !string.IsNullOrEmpty(appSettings.OpenTelemetry?.LogsEndpoint) )
{
logging.AddOpenTelemetry(
builder =>
builder.AddOtlpExporter(
options =>
{
options.Protocol = OtlpExportProtocol.HttpProtobuf;
options.Headers = appSettings.OpenTelemetry.GetLogsHeader();
options.Endpoint =
new Uri(appSettings.OpenTelemetry.LogsEndpoint);
})
.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService(appSettings.OpenTelemetry.GetServiceName())
.AddAttributes([HostNameKeyValue])
)
);
}
});

services.AddScoped<IWebLogger, WebLogger>();
}
services.AddScoped<IWebLogger, WebLogger>();
}
}
Loading

0 comments on commit bea959e

Please sign in to comment.