Skip to content

Commit 7820d2c

Browse files
authored
Support microsoft logging extensions with inline programs (pulumi#7117)
* Demystify serilog logger messages https://github.com/benaadams/Ben.Demystifier * Update changelog
1 parent 7ff1491 commit 7820d2c

24 files changed

+314
-110
lines changed

CHANGELOG_PENDING.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
### Improvements
22

3+
- [dotnet/sdk] Support microsoft logging extensions with inline programs
4+
[#7117](https://github.com/pulumi/pulumi/pull/7117)
5+
36
- [dotnet/sdk] Add create unknown to output utilities.
47
[#7173](https://github.com/pulumi/pulumi/pull/7173)
58

sdk/dotnet/Pulumi.Automation.Tests/LocalWorkspaceTests.cs

Lines changed: 127 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@
1010
using System.Threading;
1111
using System.Threading.Tasks;
1212
using Microsoft.Extensions.DependencyInjection;
13+
using Microsoft.Extensions.Logging;
1314
using Pulumi.Automation.Commands.Exceptions;
1415
using Pulumi.Automation.Events;
1516
using Pulumi.Automation.Exceptions;
1617
using Semver;
18+
using Serilog;
19+
using Serilog.Extensions.Logging;
1720
using Xunit;
21+
using Xunit.Abstractions;
22+
using ILogger = Microsoft.Extensions.Logging.ILogger;
1823

1924
namespace Pulumi.Automation.Tests
2025
{
@@ -50,6 +55,20 @@ private static string NormalizeConfigKey(string key, string projectName)
5055
return string.Empty;
5156
}
5257

58+
private ILogger TestLogger { get; }
59+
60+
public LocalWorkspaceTests(ITestOutputHelper output)
61+
{
62+
var logger = new LoggerConfiguration()
63+
.MinimumLevel.Verbose()
64+
.WriteTo.TestOutput(output, outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
65+
.CreateLogger();
66+
67+
var loggerFactory = new SerilogLoggerFactory(logger);
68+
69+
TestLogger = loggerFactory.CreateLogger<LocalWorkspaceTests>();
70+
}
71+
5372
[Theory]
5473
[InlineData("yaml")]
5574
[InlineData("yml")]
@@ -725,27 +744,27 @@ public async Task HandlesEvents()
725744
try
726745
{
727746
// pulumi preview
728-
var previewResult = await RunCommand<PreviewResult, PreviewOptions>(stack.PreviewAsync, "preview");
747+
var previewResult = await RunCommand(stack.PreviewAsync, "preview", new PreviewOptions());
729748
Assert.True(previewResult.ChangeSummary.TryGetValue(OperationType.Create, out var createCount));
730749
Assert.Equal(1, createCount);
731750

732751
// pulumi up
733-
var upResult = await RunCommand<UpResult, UpOptions>(stack.UpAsync, "up");
752+
var upResult = await RunCommand(stack.UpAsync, "up", new UpOptions());
734753
Assert.Equal(UpdateKind.Update, upResult.Summary.Kind);
735754
Assert.Equal(UpdateState.Succeeded, upResult.Summary.Result);
736755

737756
// pulumi preview
738-
var previewResultAgain = await RunCommand<PreviewResult, PreviewOptions>(stack.PreviewAsync, "preview");
757+
var previewResultAgain = await RunCommand(stack.PreviewAsync, "preview", new PreviewOptions());
739758
Assert.True(previewResultAgain.ChangeSummary.TryGetValue(OperationType.Same, out var sameCount));
740759
Assert.Equal(1, sameCount);
741760

742761
// pulumi refresh
743-
var refreshResult = await RunCommand<UpdateResult, RefreshOptions>(stack.RefreshAsync, "refresh");
762+
var refreshResult = await RunCommand(stack.RefreshAsync, "refresh", new RefreshOptions());
744763
Assert.Equal(UpdateKind.Refresh, refreshResult.Summary.Kind);
745764
Assert.Equal(UpdateState.Succeeded, refreshResult.Summary.Result);
746765

747766
// pulumi destroy
748-
var destroyResult = await RunCommand<UpdateResult, DestroyOptions>(stack.DestroyAsync, "destroy");
767+
var destroyResult = await RunCommand(stack.DestroyAsync, "destroy", new DestroyOptions());
749768
Assert.Equal(UpdateKind.Destroy, destroyResult.Summary.Kind);
750769
Assert.Equal(UpdateState.Succeeded, destroyResult.Summary.Result);
751770
}
@@ -754,12 +773,12 @@ public async Task HandlesEvents()
754773
await stack.Workspace.RemoveStackAsync(stackName);
755774
}
756775

757-
static async Task<T> RunCommand<T, TOptions>(Func<TOptions, CancellationToken, Task<T>> func, string command)
776+
static async Task<T> RunCommand<T, TOptions>(Func<TOptions, CancellationToken, Task<T>> func, string command, TOptions options)
758777
where TOptions : UpdateOptions, new()
759778
{
760779
var events = new List<EngineEvent>();
761-
762-
var result = await func(new TOptions { OnEvent = events.Add }, CancellationToken.None);
780+
options.OnEvent = events.Add;
781+
var result = await func(options, CancellationToken.None);
763782

764783
var seenSummaryEvent = events.Any(@event => @event.SummaryEvent != null);
765784
var seenCancelEvent = events.Any(@event => @event.CancelEvent != null);
@@ -872,17 +891,17 @@ public async Task ConfigSecretWarnings()
872891
await stack.SetAllConfigAsync(config);
873892

874893
// pulumi preview
875-
await RunCommand<PreviewResult, PreviewOptions>(stack.PreviewAsync, "preview");
894+
await RunCommand(stack.PreviewAsync, "preview", new PreviewOptions());
876895

877896
// pulumi up
878-
await RunCommand<UpResult, UpOptions>(stack.UpAsync, "up");
897+
await RunCommand(stack.UpAsync, "up", new UpOptions());
879898
}
880899
finally
881900
{
882901
await stack.Workspace.RemoveStackAsync(stackName);
883902
}
884903

885-
static async Task<T> RunCommand<T, TOptions>(Func<TOptions, CancellationToken, Task<T>> func, string command)
904+
static async Task<T> RunCommand<T, TOptions>(Func<TOptions, CancellationToken, Task<T>> func, string command, TOptions options)
886905
where TOptions : UpdateOptions, new()
887906
{
888907
var expectedWarnings = new[]
@@ -927,17 +946,14 @@ static async Task<T> RunCommand<T, TOptions>(Func<TOptions, CancellationToken, T
927946
};
928947

929948
var events = new List<DiagnosticEvent>();
930-
931-
var result = await func(new TOptions
949+
options.OnEvent = @event =>
932950
{
933-
OnEvent = @event =>
951+
if (@event.DiagnosticEvent?.Severity == "warning")
934952
{
935-
if (@event.DiagnosticEvent?.Severity == "warning")
936-
{
937-
events.Add(@event.DiagnosticEvent);
938-
}
953+
events.Add(@event.DiagnosticEvent);
939954
}
940-
}, CancellationToken.None);
955+
};
956+
var result = await func(options, CancellationToken.None);
941957

942958
foreach (var expected in expectedWarnings)
943959
{
@@ -1467,10 +1483,102 @@ await Assert.ThrowsAsync<ProjectSettingsConflictException>(() =>
14671483
);
14681484
}
14691485

1486+
[Fact]
1487+
public async Task InlineProgramLoggerCanBeOverridden()
1488+
{
1489+
var program = PulumiFn.Create(() =>
1490+
{
1491+
Log.Debug("test");
1492+
});
1493+
1494+
var loggerWasInvoked = false;
1495+
var logger = new CustomLogger(() => loggerWasInvoked = true);
1496+
1497+
var stackName = $"{RandomStackName()}";
1498+
var projectName = "inline_logger_override";
1499+
1500+
using var stack = await LocalWorkspace.CreateOrSelectStackAsync(
1501+
new InlineProgramArgs(projectName, stackName, program)
1502+
{
1503+
Logger = logger,
1504+
});
1505+
1506+
// make sure workspace logger is used
1507+
await stack.PreviewAsync();
1508+
Assert.True(loggerWasInvoked);
1509+
1510+
// preview logger is used
1511+
loggerWasInvoked = false;
1512+
stack.Workspace.Logger = null;
1513+
await stack.PreviewAsync(new PreviewOptions
1514+
{
1515+
Logger = logger,
1516+
});
1517+
Assert.True(loggerWasInvoked);
1518+
1519+
// up logger is used
1520+
loggerWasInvoked = false;
1521+
await stack.UpAsync(new UpOptions
1522+
{
1523+
Logger = logger,
1524+
});
1525+
Assert.True(loggerWasInvoked);
1526+
1527+
await stack.DestroyAsync();
1528+
}
1529+
1530+
[Fact]
1531+
public async Task InlineProgramLoggerCanRedirectToTestOutput()
1532+
{
1533+
var program = PulumiFn.Create(() =>
1534+
{
1535+
Log.Info("Pulumi.Log calls appear in test output");
1536+
});
1537+
1538+
var stackName = $"{RandomStackName()}";
1539+
var projectName = "inline_logger_test_output";
1540+
1541+
using var stack = await LocalWorkspace.CreateOrSelectStackAsync(
1542+
new InlineProgramArgs(projectName, stackName, program)
1543+
{
1544+
Logger = TestLogger
1545+
});
1546+
1547+
TestLogger.LogInformation("Previewing stack...");
1548+
await stack.PreviewAsync();
1549+
1550+
TestLogger.LogInformation("Updating stack...");
1551+
await stack.UpAsync();
1552+
1553+
TestLogger.LogInformation("Destroying stack...");
1554+
await stack.DestroyAsync();
1555+
}
1556+
14701557
private string ResourcePath(string path, [CallerFilePath] string pathBase = "LocalWorkspaceTests.cs")
14711558
{
14721559
var dir = Path.GetDirectoryName(pathBase) ?? ".";
14731560
return Path.Combine(dir, path);
14741561
}
1562+
1563+
private class CustomLogger : ILogger
1564+
{
1565+
private readonly Action _action;
1566+
1567+
public CustomLogger(Action action)
1568+
{
1569+
_action = action;
1570+
}
1571+
1572+
public IDisposable BeginScope<TState>(TState state)
1573+
{
1574+
throw new NotImplementedException();
1575+
}
1576+
1577+
public bool IsEnabled(LogLevel logLevel)
1578+
=> true;
1579+
1580+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
1581+
=> _action();
1582+
}
14751583
}
14761584
}

sdk/dotnet/Pulumi.Automation.Tests/Pulumi.Automation.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<ItemGroup>
1010
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
1111
<PackageReference Include="Moq" Version="4.13.1" />
12+
<PackageReference Include="Serilog.Sinks.XUnit" Version="2.0.4" />
1213
<PackageReference Include="xunit" Version="2.4.1" />
1314
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
1415
</ItemGroup>
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2-
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
3-
"parallelizeTestCollections": true
2+
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
3+
"diagnosticMessages": true,
4+
"parallelizeTestCollections": true
45
}

sdk/dotnet/Pulumi.Automation/LocalWorkspace.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Linq;
88
using System.Threading;
99
using System.Threading.Tasks;
10+
using Microsoft.Extensions.Logging;
1011
using Pulumi.Automation.Commands;
1112
using Pulumi.Automation.Exceptions;
1213
using Pulumi.Automation.Serialization;
@@ -53,6 +54,9 @@ public sealed class LocalWorkspace : Workspace
5354
/// <inheritdoc/>
5455
public override PulumiFn? Program { get; set; }
5556

57+
/// <inheritdoc/>
58+
public override ILogger? Logger { get; set; }
59+
5660
/// <inheritdoc/>
5761
public override IDictionary<string, string?>? EnvironmentVariables { get; set; }
5862

@@ -307,6 +311,7 @@ internal LocalWorkspace(
307311

308312
this.PulumiHome = options.PulumiHome;
309313
this.Program = options.Program;
314+
this.Logger = options.Logger;
310315
this.SecretsProvider = options.SecretsProvider;
311316

312317
if (options.EnvironmentVariables != null)

sdk/dotnet/Pulumi.Automation/LocalWorkspaceOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2016-2021, Pulumi Corporation
22

33
using System.Collections.Generic;
4+
using Microsoft.Extensions.Logging;
45

56
namespace Pulumi.Automation
67
{
@@ -34,6 +35,12 @@ public class LocalWorkspaceOptions
3435
/// </summary>
3536
public PulumiFn? Program { get; set; }
3637

38+
/// <summary>
39+
/// A custom logger instance that will be used for inline programs. Note that it will only be used
40+
/// if <see cref="Program"/> is also provided.
41+
/// </summary>
42+
public ILogger? Logger { get; set; }
43+
3744
/// <summary>
3845
/// Environment values scoped to the current workspace. These will be supplied to every
3946
/// Pulumi command.

sdk/dotnet/Pulumi.Automation/PreviewOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2016-2021, Pulumi Corporation
22

33
using System.Collections.Generic;
4+
using Microsoft.Extensions.Logging;
45

56
namespace Pulumi.Automation
67
{
@@ -18,5 +19,11 @@ public sealed class PreviewOptions : UpdateOptions
1819
public bool? TargetDependents { get; set; }
1920

2021
public PulumiFn? Program { get; set; }
22+
23+
/// <summary>
24+
/// A custom logger instance that will be used for the action. Note that it will only be used
25+
/// if <see cref="Program"/> is also provided.
26+
/// </summary>
27+
public ILogger? Logger { get; set; }
2128
}
2229
}

sdk/dotnet/Pulumi.Automation/PublicAPI.Shipped.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ Pulumi.Automation.LocalWorkspaceOptions
116116
Pulumi.Automation.LocalWorkspaceOptions.EnvironmentVariables.get -> System.Collections.Generic.IDictionary<string, string>
117117
Pulumi.Automation.LocalWorkspaceOptions.EnvironmentVariables.set -> void
118118
Pulumi.Automation.LocalWorkspaceOptions.LocalWorkspaceOptions() -> void
119+
Pulumi.Automation.LocalWorkspaceOptions.Logger.get -> Microsoft.Extensions.Logging.ILogger
120+
Pulumi.Automation.LocalWorkspaceOptions.Logger.set -> void
119121
Pulumi.Automation.LocalWorkspaceOptions.Program.get -> Pulumi.Automation.PulumiFn
120122
Pulumi.Automation.LocalWorkspaceOptions.Program.set -> void
121123
Pulumi.Automation.LocalWorkspaceOptions.ProjectSettings.get -> Pulumi.Automation.ProjectSettings
@@ -165,6 +167,8 @@ Pulumi.Automation.PreviewOptions.ExpectNoChanges.get -> bool?
165167
Pulumi.Automation.PreviewOptions.ExpectNoChanges.set -> void
166168
Pulumi.Automation.PreviewOptions.Diff.get -> bool?
167169
Pulumi.Automation.PreviewOptions.Diff.set -> void
170+
Pulumi.Automation.PreviewOptions.Logger.get -> Microsoft.Extensions.Logging.ILogger
171+
Pulumi.Automation.PreviewOptions.Logger.set -> void
168172
Pulumi.Automation.PreviewOptions.PreviewOptions() -> void
169173
Pulumi.Automation.PreviewOptions.Program.get -> Pulumi.Automation.PulumiFn
170174
Pulumi.Automation.PreviewOptions.Program.set -> void
@@ -271,6 +275,8 @@ Pulumi.Automation.UpOptions.ExpectNoChanges.get -> bool?
271275
Pulumi.Automation.UpOptions.ExpectNoChanges.set -> void
272276
Pulumi.Automation.UpOptions.Diff.get -> bool?
273277
Pulumi.Automation.UpOptions.Diff.set -> void
278+
Pulumi.Automation.UpOptions.Logger.get -> Microsoft.Extensions.Logging.ILogger
279+
Pulumi.Automation.UpOptions.Logger.set -> void
274280
Pulumi.Automation.UpOptions.Program.get -> Pulumi.Automation.PulumiFn
275281
Pulumi.Automation.UpOptions.Program.set -> void
276282
Pulumi.Automation.UpOptions.Replace.get -> System.Collections.Generic.List<string>
@@ -356,6 +362,8 @@ abstract Pulumi.Automation.Workspace.GetStackSettingsAsync(string stackName, Sys
356362
abstract Pulumi.Automation.Workspace.InstallPluginAsync(string name, string version, Pulumi.Automation.PluginKind kind = Pulumi.Automation.PluginKind.Resource, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
357363
abstract Pulumi.Automation.Workspace.ListPluginsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Immutable.ImmutableList<Pulumi.Automation.PluginInfo>>
358364
abstract Pulumi.Automation.Workspace.ListStacksAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Immutable.ImmutableList<Pulumi.Automation.StackSummary>>
365+
abstract Pulumi.Automation.Workspace.Logger.get -> Microsoft.Extensions.Logging.ILogger
366+
abstract Pulumi.Automation.Workspace.Logger.set -> void
359367
abstract Pulumi.Automation.Workspace.PostCommandCallbackAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
360368
abstract Pulumi.Automation.Workspace.Program.get -> Pulumi.Automation.PulumiFn
361369
abstract Pulumi.Automation.Workspace.Program.set -> void
@@ -387,6 +395,8 @@ override Pulumi.Automation.LocalWorkspace.GetStackSettingsAsync(string stackName
387395
override Pulumi.Automation.LocalWorkspace.InstallPluginAsync(string name, string version, Pulumi.Automation.PluginKind kind = Pulumi.Automation.PluginKind.Resource, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
388396
override Pulumi.Automation.LocalWorkspace.ListPluginsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Immutable.ImmutableList<Pulumi.Automation.PluginInfo>>
389397
override Pulumi.Automation.LocalWorkspace.ListStacksAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Immutable.ImmutableList<Pulumi.Automation.StackSummary>>
398+
override Pulumi.Automation.LocalWorkspace.Logger.get -> Microsoft.Extensions.Logging.ILogger
399+
override Pulumi.Automation.LocalWorkspace.Logger.set -> void
390400
override Pulumi.Automation.LocalWorkspace.PostCommandCallbackAsync(string stackName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
391401
override Pulumi.Automation.LocalWorkspace.Program.get -> Pulumi.Automation.PulumiFn
392402
override Pulumi.Automation.LocalWorkspace.Program.set -> void

0 commit comments

Comments
 (0)