Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private async Task ApplyAsync()
}
catch (InvalidOperationException invalidOperation)
{
TraceLogger.Warn(
TraceLogger.Warning(
$"DatabaseRecoveryDialog: skipped '{fileName}' ({action}) — {invalidOperation.Message}");
continue;
}
Expand Down
12 changes: 6 additions & 6 deletions src/EventLogExpert.Components/Menu/MenuBar.razor.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// // Copyright (c) Microsoft Corporation.
// // Licensed under the MIT License.

using EventLogExpert.Eventing.Readers;
using EventLogExpert.Eventing.Common.Channels;
using EventLogExpert.UI;
using EventLogExpert.UI.Interfaces;
using EventLogExpert.UI.Models;
Expand Down Expand Up @@ -129,9 +129,9 @@ private IReadOnlyList<MenuItem> BuildOpenSubMenu(bool combineLog)
MenuItem.Item("Folder", () => Actions.OpenFolderAsync(combineLog)),
MenuItem.SubMenu("Live",
[
MenuItem.Item(LogNames.ApplicationLog, () => Actions.OpenLiveLogAsync(LogNames.ApplicationLog, combineLog)),
MenuItem.Item(LogNames.SystemLog, () => Actions.OpenLiveLogAsync(LogNames.SystemLog, combineLog)),
MenuItem.Item(LogNames.SecurityLog, () => Actions.OpenLiveLogAsync(LogNames.SecurityLog, combineLog), isEnabled: isAdmin),
MenuItem.Item(LogChannelNames.ApplicationLog, () => Actions.OpenLiveLogAsync(LogChannelNames.ApplicationLog, combineLog)),
MenuItem.Item(LogChannelNames.SystemLog, () => Actions.OpenLiveLogAsync(LogChannelNames.SystemLog, combineLog)),
MenuItem.Item(LogChannelNames.SecurityLog, () => Actions.OpenLiveLogAsync(LogChannelNames.SecurityLog, combineLog), isEnabled: isAdmin),
MenuItem.AsyncSubMenu(
"Other Logs",
async () => BuildOtherLogsTree(await Actions.GetOtherLogNamesAsync(), combineLog, isAdmin)),
Expand All @@ -146,12 +146,12 @@ private IReadOnlyList<MenuItem> BuildOtherLogsTree(IReadOnlyList<string> logName

foreach (var logName in logNames)
{
var path = LogNameMethods.GetMenuPath(logName);
var path = LogChannelMethods.GetMenuPath(logName);

if (path.Count == 0) { continue; }

var log = path[^1];
var logIsEnabled = isAdmin || !LogNames.AdminOnlyLiveLogNames.Contains(logName);
var logIsEnabled = isAdmin || !LogChannelNames.AdminOnlyLiveChannels.Contains(logName);
var logMenuItem = MenuItem.Item(log, () => Actions.OpenLiveLogAsync(logName, combineLog), isEnabled: logIsEnabled);

if (path.Count == 1)
Expand Down
28 changes: 14 additions & 14 deletions src/EventLogExpert.EventDbTool/CreateDatabaseCommand.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// // Copyright (c) Microsoft Corporation.
// // Licensed under the MIT License.

using EventLogExpert.Eventing.EventProviderDatabase;
using EventLogExpert.Eventing.Logging;
using EventLogExpert.Eventing.ProviderDatabase;
using EventLogExpert.Eventing.Providers;
using Microsoft.Extensions.DependencyInjection;
using System.CommandLine;
Expand Down Expand Up @@ -101,23 +101,23 @@ private void CreateDatabase(string path, string? source, string? filter, string?
skipProviderNames.Add(name);
}

Logger.Info($"Found {skipProviderNames.Count} providers in {skipProvidersInFile}. These will not be included in the new database.");
Logger.Information($"Found {skipProviderNames.Count} providers in {skipProvidersInFile}. These will not be included in the new database.");
}

// Stream details directly into the DbContext. For .evtx sources, scanning the file
// is expensive, so we deliberately do NOT pre-scan provider names just to size the
// log header — that would cause a second full pass over the .evtx. Instead we
// buffer the first batch of resolved details, derive the header column width from
// those names, then log the header + buffered rows and continue streaming.
const int batchSize = 100;
const int BatchSize = 100;
var count = 0;
var headerLogged = false;
var pendingForHeader = new List<ProviderDetails>(batchSize);
var pendingForHeader = new List<ProviderDetails>(BatchSize);

// Defer creating the DbContext (and therefore the .db file on disk) until we have
// at least one provider to persist. This prevents leaving an empty database behind
// when no provider details could be resolved (e.g., .evtx without LocaleMetaData).
EventProviderDbContext? dbContext = null;
ProviderDbContext? dbContext = null;

try
{
Expand All @@ -131,19 +131,19 @@ private void CreateDatabase(string path, string? source, string? filter, string?
{
pendingForHeader.Add(details);

if (pendingForHeader.Count < batchSize) { continue; }
if (pendingForHeader.Count < BatchSize) { continue; }

FlushHeaderAndBuffer(ref dbContext, path, pendingForHeader, ref count);
headerLogged = true;
continue;
}

dbContext ??= new EventProviderDbContext(path, false, Logger);
dbContext ??= new ProviderDbContext(path, false, Logger);
dbContext.ProviderDetails.Add(details);
LogProviderDetails(details);
count++;

if (count % batchSize != 0) { continue; }
if (count % BatchSize != 0) { continue; }

dbContext.SaveChanges();
dbContext.ChangeTracker.Clear();
Expand All @@ -157,17 +157,17 @@ private void CreateDatabase(string path, string? source, string? filter, string?

if (dbContext is null)
{
Logger.Warn($"No provider details could be resolved from the source. Database was not created.");
Logger.Warning($"No provider details could be resolved from the source. Database was not created.");

return;
}

Logger.Info($"");
Logger.Info($"Saving database. Please wait...");
Logger.Information($"");
Logger.Information($"Saving database. Please wait...");

dbContext.SaveChanges();

Logger.Info($"Done!");
Logger.Information($"Done!");
}
finally
{
Expand All @@ -181,14 +181,14 @@ private void CreateDatabase(string path, string? source, string? filter, string?
}

private void FlushHeaderAndBuffer(
ref EventProviderDbContext? dbContext,
ref ProviderDbContext? dbContext,
string path,
List<ProviderDetails> buffer,
ref int count)
{
LogProviderDetailHeader(buffer.Select(p => p.ProviderName));

dbContext ??= new EventProviderDbContext(path, false, Logger);
dbContext ??= new ProviderDbContext(path, false, Logger);

foreach (var details in buffer)
{
Expand Down
4 changes: 2 additions & 2 deletions src/EventLogExpert.EventDbTool/DbToolCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ protected void LogProviderDetailHeader(IEnumerable<string> providerNames)
_providerDetailFormat = "{0, -" + maxNameLength + "} {1, 8} {2, 8} {3, 8} {4, 8} {5, 8} {6, 8}";

var header = string.Format(_providerDetailFormat, "Provider Name", "Events", "Parameters", "Keywords", "Opcodes", "Tasks", "Messages");
Logger.Info($"{header}");
Logger.Information($"{header}");
}

protected void LogProviderDetails(ProviderDetails details)
Expand All @@ -69,6 +69,6 @@ protected void LogProviderDetails(ProviderDetails details)
details.Tasks.Count,
details.Messages.Count);

Logger.Info($"{line}");
Logger.Information($"{line}");
}
}
18 changes: 10 additions & 8 deletions src/EventLogExpert.EventDbTool/DiffDatabaseCommand.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// // Copyright (c) Microsoft Corporation.
// // Licensed under the MIT License.

using EventLogExpert.Eventing.EventProviderDatabase;
using EventLogExpert.Eventing.Logging;
using EventLogExpert.Eventing.ProviderDatabase;
using EventLogExpert.Eventing.Providers;
using Microsoft.Extensions.DependencyInjection;
using System.CommandLine;
Expand Down Expand Up @@ -69,12 +69,14 @@ internal void DiffDatabase(string firstSource, string secondSource, string newDb
if (File.Exists(newDb))
{
Logger.Error($"File already exists: {newDb}");

return;
}

if (!string.Equals(Path.GetExtension(newDb), ".db", StringComparison.OrdinalIgnoreCase))
{
Logger.Error($"New db path must have a .db extension.");

return;
}

Expand All @@ -87,20 +89,20 @@ internal void DiffDatabase(string firstSource, string secondSource, string newDb
// Pass firstProviderNames as the skip set so providers present in the first source are
// never resolved from the second source's metadata path. This is especially important when
// the second source is .evtx+MTA, where each provider triggers an expensive load.
Logger.Info($"Skipping up to {firstProviderNames.Count} provider name(s) from the second source that also appear in the first source.");
Logger.Information($"Skipping up to {firstProviderNames.Count} provider name(s) from the second source that also appear in the first source.");

// Defer creating the DbContext (and therefore the .db file on disk) until at least one
// provider is actually about to be persisted. This prevents leaving an empty database
// behind when the second source yields no new providers.
EventProviderDbContext? newDbContext = null;
ProviderDbContext? newDbContext = null;

try
{
foreach (var details in ProviderSource.LoadProviders(secondSource, Logger, filter: null, skipProviderNames: firstProviderNames))
{
Logger.Info($"Copying {details.ProviderName} because it is present in second source but not first.");
Logger.Information($"Copying {details.ProviderName} because it is present in second source but not first.");

newDbContext ??= new EventProviderDbContext(newDb, false, Logger);
newDbContext ??= new ProviderDbContext(newDb, false, Logger);

newDbContext.ProviderDetails.Add(details);

Expand All @@ -109,14 +111,14 @@ internal void DiffDatabase(string firstSource, string secondSource, string newDb

if (newDbContext is null)
{
Logger.Warn($"No providers in the second source are missing from the first. Database was not created.");
Logger.Warning($"No providers in the second source are missing from the first. Database was not created.");
return;
}

newDbContext.SaveChanges();

Logger.Info($"Providers copied to new database:");
Logger.Info($"");
Logger.Information($"Providers copied to new database:");
Logger.Information($"");
LogProviderDetailHeader(providersCopied.Select(p => p.ProviderName));

foreach (var provider in providersCopied)
Expand Down
38 changes: 21 additions & 17 deletions src/EventLogExpert.EventDbTool/MergeDatabaseCommand.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// // Copyright (c) Microsoft Corporation.
// // Licensed under the MIT License.

using EventLogExpert.Eventing.EventProviderDatabase;
using EventLogExpert.Eventing.Logging;
using EventLogExpert.Eventing.ProviderDatabase;
using EventLogExpert.Eventing.Providers;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -75,7 +75,8 @@ internal void MergeDatabase(string source, string targetFile, bool overwriteProv

if (sourceNames.Count == 0)
{
Logger.Warn($"No providers were discovered in the source.");
Logger.Warning($"No providers were discovered in the source.");

return;
}

Expand All @@ -85,30 +86,33 @@ internal void MergeDatabase(string source, string targetFile, bool overwriteProv

try
{
using var probe = new EventProviderDbContext(targetFile, readOnly: false, ensureCreated: false, Logger);
using var probe = new ProviderDbContext(targetFile, readOnly: false, ensureCreated: false, Logger);
targetState = probe.IsUpgradeNeeded();
}
catch (DbException ex)
{
Logger.Error($"Failed to merge into database '{targetFile}': {ex.Message}");

return;
}

if (targetState.CurrentVersion == ProviderDatabaseSchemaVersion.Unknown)
{
Logger.Error($"{DatabaseSchemaMessages.UnrecognizedSchema(DatabaseSchemaMessages.TargetLabel, targetFile)}");
Logger.Error($"{SchemaStateMessages.UnrecognizedSchema(SchemaStateMessages.TargetLabel, targetFile)}");

return;
}

if (targetState.NeedsUpgrade)
{
Logger.Error($"Target database '{targetFile}' is at schema v{targetState.CurrentVersion} but v{ProviderDatabaseSchemaVersion.Current} is required. Run the 'upgrade' command first.");

return;
}

// Destructive ops beyond this point (ExecuteDelete commits immediately; SaveChanges writes) —
// exceptions propagate so partial-state failures aren't masked as a benign "merge failed".
using var targetContext = new EventProviderDbContext(targetFile, false, Logger);
using var targetContext = new ProviderDbContext(targetFile, false, Logger);

// Query the overlap in the database by chunking sourceNames into IN-clause batches,
// rather than pulling every target ProviderName into memory. Same chunk size as the
Expand Down Expand Up @@ -137,11 +141,11 @@ internal void MergeDatabase(string source, string targetFile, bool overwriteProv

if (targetMatchingNames.Count > 0)
{
Logger.Info($"The target database contains {targetMatchingNames.Count} provider row(s) matching {providerNamesInTarget.Count} provider name(s) in the source.");
Logger.Information($"The target database contains {targetMatchingNames.Count} provider row(s) matching {providerNamesInTarget.Count} provider name(s) in the source.");

if (overwriteProviders)
{
Logger.Info($"Removing these providers from the target database...");
Logger.Information($"Removing these providers from the target database...");

// Chunk the IN-clause to stay below SQLite's parameter limit (default 999). Without
// chunking, an --overwrite of a large overlap could throw at runtime.
Expand All @@ -160,15 +164,15 @@ internal void MergeDatabase(string source, string targetFile, bool overwriteProv
.ExecuteDelete();
}

Logger.Info($"Removal of {removed} provider row(s) completed.");
Logger.Information($"Removal of {removed} provider row(s) completed.");
}
else
{
Logger.Info($"These providers will not be copied from the source.");
Logger.Information($"These providers will not be copied from the source.");
}
}

Logger.Info($"Copying providers from the source...");
Logger.Information($"Copying providers from the source...");

// When not overwriting, pass the overlap as the skip set so providers that already exist
// in the target are never resolved from the source's metadata path. When overwriting, no
Expand All @@ -179,23 +183,23 @@ internal void MergeDatabase(string source, string targetFile, bool overwriteProv
? sourceNames.ToList()
: sourceNames.Where(n => !skipForLoad.Contains(n)).ToList();

Logger.Info($"");
Logger.Information($"");
LogProviderDetailHeader(expectedCopiedNames);

// Stream details into the DbContext with periodic SaveChanges. The pending batch list is
// bounded by batchSize so memory stays flat regardless of source size; details are logged
// AFTER each successful SaveChanges so the printed rows reflect what actually persisted
// (a SaveChanges failure won't cause the log to overstate progress).
const int batchSize = 100;
const int BatchSize = 100;
var copiedCount = 0;
var pendingBatch = new List<ProviderDetails>(batchSize);
var pendingBatch = new List<ProviderDetails>(BatchSize);

foreach (var provider in ProviderSource.LoadProviders(source, Logger, filter: null, skipProviderNames: skipForLoad))
{
targetContext.ProviderDetails.Add(provider);
pendingBatch.Add(provider);

if (pendingBatch.Count < batchSize) { continue; }
if (pendingBatch.Count < BatchSize) { continue; }

FlushBatch(targetContext, pendingBatch, ref copiedCount);
}
Expand All @@ -205,11 +209,11 @@ internal void MergeDatabase(string source, string targetFile, bool overwriteProv
FlushBatch(targetContext, pendingBatch, ref copiedCount);
}

Logger.Info($"");
Logger.Info($"Copied {copiedCount} provider(s).");
Logger.Information($"");
Logger.Information($"Copied {copiedCount} provider(s).");
}

private void FlushBatch(EventProviderDbContext context, List<ProviderDetails> batch, ref int copiedCount)
private void FlushBatch(ProviderDbContext context, List<ProviderDetails> batch, ref int copiedCount)
{
context.SaveChanges();

Expand Down
Loading