Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Exceptionless.Core/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ private static async Task CreateSampleDataAsync(IServiceProvider container)

var dataHelper = container.GetRequiredService<SampleDataService>();
await dataHelper.CreateDataAsync();
await dataHelper.EnqueueSampleEventsAsync(eventCount: 100, daysBack: 7);
await dataHelper.EnqueueSampleEventsAsync();
}

public static void AddHostedJobs(IServiceCollection services, ILoggerFactory loggerFactory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,13 @@ public override async Task HandleItemAsync(WorkItemContext context)
var workItem = context.GetData<GenerateSampleEventsWorkItem>()!;
int eventCount = Math.Clamp(workItem.EventCount, 1, 10000);
int daysBack = Math.Clamp(workItem.DaysBack, 1, 365);
int acceptedDaysBack = Math.Min(daysBack, 3);

Log.LogInformation("Generating {EventCount} sample events over {DaysBack} days", eventCount, acceptedDaysBack);
await context.ReportProgressAsync(0, $"Generating {eventCount} sample events over {acceptedDaysBack} days");
Log.LogInformation("Generating {EventCount} sample events over {DaysBack} days", eventCount, daysBack);
await context.ReportProgressAsync(0, $"Generating {eventCount} sample events over {daysBack} days");

var generator = new RandomEventGenerator(_timeProvider);
var utcNow = _timeProvider.GetUtcNow().UtcDateTime;
var minDate = utcNow.AddDays(-acceptedDaysBack);
var minDate = utcNow.AddDays(-daysBack);

if (IsProjectScoped(workItem))
{
Expand Down Expand Up @@ -98,7 +97,7 @@ public override async Task HandleItemAsync(WorkItemContext context)
if (context.CancellationToken.IsCancellationRequested)
break;

await _eventPipeline.RunAsync(batch, organization, project);
await _eventPipeline.RunAsync(batch, organization, project, allowExtendedEventDateRange: true);
totalProcessed += batch.Length;

int percentage = (int)Math.Min(99, totalProcessed * 100.0 / eventCount);
Expand Down Expand Up @@ -141,7 +140,7 @@ private async Task GenerateProjectSampleEventsAsync(WorkItemContext context, Ran
if (context.CancellationToken.IsCancellationRequested)
break;

await _eventPipeline.RunAsync(batch, organization, project);
await _eventPipeline.RunAsync(batch, organization, project, allowExtendedEventDateRange: true);
totalProcessed += batch.Length;

int percentage = (int)Math.Min(99, totalProcessed * 100.0 / eventCount);
Expand Down
8 changes: 5 additions & 3 deletions src/Exceptionless.Core/Models/SavedView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ namespace Exceptionless.Core.Models;
/// </summary>
public record SavedView : IOwnedByOrganizationWithIdentity, IHaveDates
{
public const int MaxFilterDefinitionsLength = 100_000;

// Identity
[ObjectId]
public string Id { get; set; } = null!;
Expand Down Expand Up @@ -38,14 +40,14 @@ public record SavedView : IOwnedByOrganizationWithIdentity, IHaveDates
public string? Filter { get; set; }

/// <summary>JSON array of structured filter objects for UI chip hydration.</summary>
[MaxLength(10000)]
[MaxLength(MaxFilterDefinitionsLength)]
public string? FilterDefinitions { get; set; }

/// <summary>Column visibility state per dashboard table, keyed by column id.</summary>
public Dictionary<string, bool>? Columns { get; set; }

/// <summary>Whether this view loads automatically when navigating to the page.</summary>
public bool IsDefault { get; set; }
/// <summary>Column display order per dashboard table, excluding utility columns.</summary>
public List<string>? ColumnOrder { get; set; }

/// <summary>Display name shown in the sidebar and picker.</summary>
[Required]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ public record GenerateSampleEventsWorkItem
{
public string? OrganizationId { get; init; }
public string? ProjectId { get; init; }
public int EventCount { get; init; } = 100;
public int EventCount { get; init; } = 250;
public int DaysBack { get; init; } = 7;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public override Task ProcessAsync(EventContext ctx)

// Discard events that are being submitted outside of the plan retention limit.
double eventAgeInDays = _timeProvider.GetUtcNow().UtcDateTime.Subtract(ctx.Event.Date.UtcDateTime).TotalDays;
if (eventAgeInDays > 3 || ctx.Organization.RetentionDays > 0 && eventAgeInDays > ctx.Organization.RetentionDays)
if ((!ctx.AllowExtendedEventDateRange && eventAgeInDays > 3) || (ctx.Organization.RetentionDays > 0 && eventAgeInDays > ctx.Organization.RetentionDays))
{
_logger.LogInformation("Discarding event that occurred more than three days ago or outside of organization retention limit");

Expand Down
14 changes: 10 additions & 4 deletions src/Exceptionless.Core/Pipeline/EventPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@ public class EventPipeline : PipelineBase<EventContext, EventPipelineActionBase>
{
public EventPipeline(IServiceProvider serviceProvider, AppOptions options, ILoggerFactory loggerFactory) : base(serviceProvider, options, loggerFactory) { }

public Task<EventContext> RunAsync(PersistentEvent ev, Organization organization, Project project, EventPostInfo? epi = null)
public Task<EventContext> RunAsync(PersistentEvent ev, Organization organization, Project project, EventPostInfo? epi = null, bool allowExtendedEventDateRange = false)
{
return RunAsync(new EventContext(ev, organization, project, epi));
return RunAsync(new EventContext(ev, organization, project, epi)
{
AllowExtendedEventDateRange = allowExtendedEventDateRange
});
}

public Task<ICollection<EventContext>> RunAsync(IEnumerable<PersistentEvent> events, Organization organization, Project project, EventPostInfo? epi = null)
public Task<ICollection<EventContext>> RunAsync(IEnumerable<PersistentEvent> events, Organization organization, Project project, EventPostInfo? epi = null, bool allowExtendedEventDateRange = false)
{
return RunAsync(events.Select(ev => new EventContext(ev, organization, project, epi)).ToList());
return RunAsync(events.Select(ev => new EventContext(ev, organization, project, epi)
{
AllowExtendedEventDateRange = allowExtendedEventDateRange
}).ToList());
}

public override async Task<ICollection<EventContext>> RunAsync(ICollection<EventContext> contexts)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public EventContext(PersistentEvent ev, Organization organization, Project proje
public bool IsNew { get; set; }
public bool IsRegression { get; set; }
public bool IncludePrivateInformation { get; set; }
public bool AllowExtendedEventDateRange { get; set; }
public string? SignatureHash { get; set; }
public IDictionary<string, string> StackSignatureData { get; private set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public SavedViewIndex(ExceptionlessElasticConfiguration configuration) : base(co
.Keyword(f => f.Name(e => e.UpdatedByUserId))
.Text(f => f.Name(e => e.Name).Analyzer(KEYWORD_LOWERCASE_ANALYZER).AddKeywordField())
.Keyword(f => f.Name(e => e.ViewType))
.Boolean(f => f.Name(e => e.IsDefault))
.Number(f => f.Name(e => e.Version).Type(NumberType.Integer)));
}

Expand Down
31 changes: 24 additions & 7 deletions src/Exceptionless.Core/Utility/RandomEventGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public List<PersistentEvent> Generate(string organizationId, string projectId, i
// Reserve ~20% of events for sessions
int sessionCount = Math.Clamp(count / 5, 0, count);
int regularCount = count - sessionCount;
int errorLogCount = Math.Clamp(regularCount / 10, regularCount > 0 ? 1 : 0, regularCount);

for (int i = 0; i < regularCount; i++)
{
Expand All @@ -36,7 +37,7 @@ public List<PersistentEvent> Generate(string organizationId, string projectId, i
Date = RandomData.GetDateTime(min, max)
};

PopulateEvent(ev);
PopulateEvent(ev, i < errorLogCount ? "Error" : null);
events.Add(ev);
}

Expand Down Expand Up @@ -88,12 +89,12 @@ private List<PersistentEvent> GenerateSession(string organizationId, string proj
return events;
}

private void PopulateEvent(Event ev)
private void PopulateEvent(Event ev, string? logLevel = null)
{
ev.Data ??= new DataDictionary();
ev.Tags ??= [];

ev.Type = EventTypes.Random()!;
ev.Type = String.IsNullOrEmpty(logLevel) ? EventTypes.Random()! : Event.KnownTypes.Log;
switch (ev.Type)
{
case Event.KnownTypes.FeatureUsage:
Expand All @@ -104,8 +105,8 @@ private void PopulateEvent(Event ev)
break;
case Event.KnownTypes.Log:
ev.Source = LogSources.Random();
ev.Message = LogMessages.Random();
string? level = LogLevels.Random();
string? level = logLevel ?? LogLevels.Random();
ev.Message = GetLogMessage(level);
if (!String.IsNullOrEmpty(level))
ev.Data[Event.KnownDataKeys.Level] = level;
break;
Expand Down Expand Up @@ -147,8 +148,8 @@ private void PopulateEvent(Event ev)
if (ev.Type == Event.KnownTypes.Error)
{
// Pre-generate a limited set of errors so stacking occurs
_randomErrors ??= [.. Enumerable.Range(1, 15).Select(_ => GenerateError())];
_randomSimpleErrors ??= [.. Enumerable.Range(1, 10).Select(_ => GenerateSimpleError())];
_randomErrors ??= [.. Enumerable.Range(1, 5).Select(_ => GenerateError())];
_randomSimpleErrors ??= [.. Enumerable.Range(1, 3).Select(_ => GenerateSimpleError())];

if (RandomData.GetBool())
ev.Data[Event.KnownDataKeys.Error] = _randomErrors.Random();
Expand All @@ -157,6 +158,13 @@ private void PopulateEvent(Event ev)
}
}

private static string? GetLogMessage(string? level)
{
return String.Equals(level, "Error", StringComparison.OrdinalIgnoreCase)
? ErrorLogMessages.Random()
: LogMessages.Random();
}

private List<Error>? _randomErrors;
private List<SimpleError>? _randomSimpleErrors;

Expand Down Expand Up @@ -380,6 +388,15 @@ private static DataDictionary GenerateErrorData()
"Health check passed"
];

private static readonly List<string> ErrorLogMessages =
[
"Failed to process event batch after retry limit was reached",
"Unhandled exception while processing background job",
"Unable to publish notification email",
"Database command failed while saving project usage",
"Elasticsearch request failed while searching events"
];

private static readonly List<string> LogLevels =
[
"Trace", "Debug", "Info", "Info", "Warn", "Error"
Expand Down
4 changes: 2 additions & 2 deletions src/Exceptionless.Core/Utility/SampleDataService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ await _tokenRepository.AddAsync(new Token
_logger.LogDebug("Created Internal Organization {OrganizationName} and Project {ProjectName}", organization.Name, project.Name);
}

public async Task EnqueueSampleEventsAsync(int eventCount = 100, int daysBack = 7)
public async Task EnqueueSampleEventsAsync(int eventCount = 250, int daysBack = 7)
{
await _workItemQueue.EnqueueAsync(new GenerateSampleEventsWorkItem
{
Expand All @@ -287,7 +287,7 @@ await _workItemQueue.EnqueueAsync(new GenerateSampleEventsWorkItem
_logger.LogInformation("Enqueued sample event generation: {EventCount} events over {DaysBack} days", eventCount, daysBack);
}

public async Task<string> EnqueueSampleEventsAsync(string organizationId, string projectId, int eventCount = 100, int daysBack = 7)
public async Task<string> EnqueueSampleEventsAsync(string organizationId, string projectId, int eventCount = 250, int daysBack = 7)
{
string workItemId = await _workItemQueue.EnqueueAsync(new GenerateSampleEventsWorkItem
{
Expand Down
28 changes: 16 additions & 12 deletions src/Exceptionless.Web/ClientApp/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@
--sidebar-border: var(--border);
--sidebar-ring: var(--ring);

--chart-1: #7bb662; /* Total (green, light) */
--chart-2: #56b4e9; /* Blocked (blue, light) */
--chart-3: #d47a00; /* Discarded – hsl(32 100% 42%) */
--chart-4: #ffd64d; /* Too Big – hsl(46 100% 65%) */
--chart-5: #d9d9d9; /* Total in Organization (magenta, light) */
--chart-6: #c62828; /* material-red-700: deep red for light mode */
--chart-1: #72c928; /* Total / events: classic Exceptionless green */
--chart-2: #43a047; /* Secondary series: deeper green */
--chart-3: #f0ad4e; /* Discarded / regressed: warm amber */
--chart-4: #f7d34a; /* Ignored / too big: golden yellow */
--chart-5: #8a8f98; /* Organization total / snoozed: neutral gray */
--chart-6: #d9534f; /* Limit / destructive: classic red */
}

.dark {
Expand Down Expand Up @@ -91,12 +91,12 @@
--sidebar-border: var(--border);
--sidebar-ring: var(--ring);

--chart-1: #a4d56f; /* Total (green, dark) */
--chart-2: #8fdbff; /* Blocked (blue, dark) */
--chart-3: #ff9e3d; /* Discarded – hsl(30 100% 62%) */
--chart-4: #ffea70; /* Too Big – hsl(48 100% 70%) */
--chart-5: #5a5a5a; /* Total in Organization (magenta, dark) */
--chart-6: #ff5c5c; /* hsl(0 100% 66%): bright red for dark mode */
--chart-1: #91dc45; /* Total / events: classic Exceptionless green */
--chart-2: #5fc263; /* Secondary series: deeper green */
--chart-3: #ffbd63; /* Discarded / regressed: warm amber */
--chart-4: #ffe16a; /* Ignored / too big: golden yellow */
--chart-5: #a4abb5; /* Organization total / snoozed: neutral gray */
--chart-6: #ff746f; /* Limit / destructive: classic red */
}

@theme inline {
Expand Down Expand Up @@ -207,6 +207,10 @@
}

/* HACK: Need to figure out why this is needed for range selection. */
[data-slot='chart'] .lc-area-path {
fill-opacity: 0.16;
}

.lc-brush-range {
@apply bg-primary/10 border-primary border-2;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,12 @@
<DropdownMenu.GroupHeading>Actions</DropdownMenu.GroupHeading>
<DropdownMenu.Separator />
{#if canToggle}
<DropdownMenu.Item onclick={onToggleView} title="Toggle between raw and structured view">
<DropdownMenu.Item onclick={onToggleView} title="Toggle Between Raw and Structured View">
<ToggleLeft class="mr-2 size-4" />
Toggle View
</DropdownMenu.Item>
{/if}
<DropdownMenu.Item onclick={copyToClipboard} title="Copy to clipboard">
<DropdownMenu.Item onclick={copyToClipboard} title="Copy to Clipboard">
<Copy class="mr-2 size-4" />
Copy to Clipboard
</DropdownMenu.Item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@

let { level }: Props = $props();

function getLogLevelVariant(level: LogLevel | null): 'default' | 'destructive' | 'outline' | 'secondary' {
function getLogLevelVariant(level: LogLevel | null): 'default' | 'destructive' | 'info' | 'outline' | 'secondary' | 'yellow' {
if (level === 'trace' || level === 'debug') {
return 'secondary';
}

if (level === 'info') {
return 'default';
return 'info';
}

if (level === 'warn' || level === 'error') {
if (level === 'warn') {
return 'yellow';
}

if (level === 'error') {
return 'destructive';
}

Expand Down
Loading