Skip to content
Open
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
4 changes: 4 additions & 0 deletions .github/workflows/nuget-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ jobs:
path: |
src/Temporalio/bin/Release/*.nupkg
src/Temporalio/bin/Release/*.snupkg
src/Temporalio.Extensions.Aws.Lambda/bin/Release/*.nupkg
Comment thread
jmaeagle99 marked this conversation as resolved.
src/Temporalio.Extensions.Aws.Lambda/bin/Release/*.snupkg
src/Temporalio.Extensions.Aws.Lambda.OpenTelemetry/bin/Release/*.nupkg
src/Temporalio.Extensions.Aws.Lambda.OpenTelemetry/bin/Release/*.snupkg
src/Temporalio.Extensions.DiagnosticSource/bin/Release/*.nupkg
src/Temporalio.Extensions.DiagnosticSource/bin/Release/*.snupkg
src/Temporalio.Extensions.Hosting/bin/Release/*.nupkg
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ to docs, or any other relevant information.

- Fixed `ClientEnvConfig` empty `OverrideEnvVars` handling so an explicit empty dictionary no
longer falls back to process environment variables.
### Breaking Changes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither of these changes are accurate. All of this code is new so it cannot be breaking nor changed. There should be one entry in Added that describes the creation of these Lambda support packages.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed that the AI had updated that. 😳


- Removed public `TemporalLambdaWorkerOptions.ShutdownHooks` list mutation. Use
`TemporalLambdaWorkerOptions.AddShutdownHook(...)` to register Lambda shutdown hooks.

### Changed

- Synchronous `TemporalLambdaWorker.CreateHandler` configuration callbacks now run once per Lambda
invocation, matching asynchronous configuration callbacks.

### [1.16.0] - 2026-07-01

Expand Down
4 changes: 4 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Amazon.Lambda.Core" Version="3.1.0" />
<PackageVersion Include="Google.Protobuf" Version="3.26.1" />
<PackageVersion Include="MartinCostello.Logging.XUnit" Version="0.7.1" />
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
Expand All @@ -15,8 +16,11 @@
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Microsoft.VisualStudio.Threading.Analyzers" Version="18.7.23" />
<PackageVersion Include="NexusRpc" Version="0.3.0" />
<PackageVersion Include="OpenTelemetry" Version="1.15.3" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add the new OpenTelemetry.* packages to the ignore group in https://github.com/temporalio/sdk-dotnet/blob/main/.github/dependabot.yml#L68 so that way we don't get constantly pestered to update but still get security updates.

<PackageVersion Include="OpenTelemetry.Api" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Exporter.InMemory" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Extensions.AWS" Version="1.15.1" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.435" />
<PackageVersion Include="System.CommandLine" Version="2.0.8" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="7.0.0" />
Expand Down
14 changes: 14 additions & 0 deletions Temporalio.sln
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Temporalio.Extensions.Hosti
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Temporalio.Extensions.DiagnosticSource", "src\Temporalio.Extensions.DiagnosticSource\Temporalio.Extensions.DiagnosticSource.csproj", "{CC7EA7CD-BBE7-448C-8A4B-F8B2D1E55990}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Temporalio.Extensions.Aws.Lambda", "src\Temporalio.Extensions.Aws.Lambda\Temporalio.Extensions.Aws.Lambda.csproj", "{B7CDF2C9-1D1D-446C-AF90-6C758D7DF19D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Temporalio.SimpleBench", "tests\Temporalio.SimpleBench\Temporalio.SimpleBench.csproj", "{2610AFAE-FD3A-4583-8CA5-4869E1347A3C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Temporalio.Extensions.Aws.Lambda.OpenTelemetry", "src\Temporalio.Extensions.Aws.Lambda.OpenTelemetry\Temporalio.Extensions.Aws.Lambda.OpenTelemetry.csproj", "{9A2C7274-7ED2-4C92-BE92-13887C3309B4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -48,17 +52,27 @@ Global
{CC7EA7CD-BBE7-448C-8A4B-F8B2D1E55990}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC7EA7CD-BBE7-448C-8A4B-F8B2D1E55990}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC7EA7CD-BBE7-448C-8A4B-F8B2D1E55990}.Release|Any CPU.Build.0 = Release|Any CPU
{B7CDF2C9-1D1D-446C-AF90-6C758D7DF19D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7CDF2C9-1D1D-446C-AF90-6C758D7DF19D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7CDF2C9-1D1D-446C-AF90-6C758D7DF19D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7CDF2C9-1D1D-446C-AF90-6C758D7DF19D}.Release|Any CPU.Build.0 = Release|Any CPU
{2610AFAE-FD3A-4583-8CA5-4869E1347A3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2610AFAE-FD3A-4583-8CA5-4869E1347A3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2610AFAE-FD3A-4583-8CA5-4869E1347A3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2610AFAE-FD3A-4583-8CA5-4869E1347A3C}.Release|Any CPU.Build.0 = Release|Any CPU
{9A2C7274-7ED2-4C92-BE92-13887C3309B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A2C7274-7ED2-4C92-BE92-13887C3309B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A2C7274-7ED2-4C92-BE92-13887C3309B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A2C7274-7ED2-4C92-BE92-13887C3309B4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{7AE1422A-0937-40D7-9A62-431DD0E2F6D5} = {758B61E2-9AB6-46BF-B53C-16BD140BF56B}
{D5F245E2-73A2-49C6-8C52-FBE892E87169} = {F2683DAA-F157-448E-96C8-DF7BB019886D}
{D4AC2E2B-1C24-491D-9175-874D448D30FE} = {758B61E2-9AB6-46BF-B53C-16BD140BF56B}
{E8D1975A-5AF7-4375-BAD0-3C256DCB7F87} = {758B61E2-9AB6-46BF-B53C-16BD140BF56B}
{CC7EA7CD-BBE7-448C-8A4B-F8B2D1E55990} = {758B61E2-9AB6-46BF-B53C-16BD140BF56B}
{B7CDF2C9-1D1D-446C-AF90-6C758D7DF19D} = {758B61E2-9AB6-46BF-B53C-16BD140BF56B}
{2610AFAE-FD3A-4583-8CA5-4869E1347A3C} = {F2683DAA-F157-448E-96C8-DF7BB019886D}
{9A2C7274-7ED2-4C92-BE92-13887C3309B4} = {758B61E2-9AB6-46BF-B53C-16BD140BF56B}
EndGlobalSection
EndGlobal
4 changes: 3 additions & 1 deletion src/Temporalio.ApiDoc/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Commonly used namespaces:

Extensions:

* [Temporalio.Extensions.Aws.Lambda](/api/Temporalio.Extensions.Aws.Lambda.html)
Comment thread
jmaeagle99 marked this conversation as resolved.
* [Temporalio.Extensions.Aws.Lambda.OpenTelemetry](/api/Temporalio.Extensions.Aws.Lambda.OpenTelemetry.html)
* [Temporalio.Extensions.DiagnosticSource](/api/Temporalio.Extensions.DiagnosticSource.html)
* [Temporalio.Extensions.Hosting](/api/Temporalio.Extensions.Hosting.html)
* [Temporalio.Extensions.OpenTelemetry](/api/Temporalio.Extensions.OpenTelemetry.html)
* [Temporalio.Extensions.OpenTelemetry](/api/Temporalio.Extensions.OpenTelemetry.html)
4 changes: 3 additions & 1 deletion src/Temporalio.ApiDoc/docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
{
"files": [
"Temporalio/*.csproj",
"Temporalio.Extensions.Aws.Lambda/*.csproj",
Comment thread
jmaeagle99 marked this conversation as resolved.
"Temporalio.Extensions.Aws.Lambda.OpenTelemetry/*.csproj",
"Temporalio.Extensions.DiagnosticSource/*.csproj",
"Temporalio.Extensions.Hosting/*.csproj",
"Temporalio.Extensions.OpenTelemetry/*.csproj"
Expand Down Expand Up @@ -72,4 +74,4 @@
"keepFileLink": false,
"disableGitFeatures": false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Temporalio.Client.Interceptors;
using Temporalio.Runtime;
using TemporalOpenTelemetry = Temporalio.Extensions.OpenTelemetry;

namespace Temporalio.Extensions.Aws.Lambda.OpenTelemetry
{
/// <summary>
/// OpenTelemetry helpers for Temporal workers running inside AWS Lambda.
/// </summary>
public static class LambdaWorkerOpenTelemetry
{
private const string DefaultCollectorEndpoint = "http://localhost:4317";
private const string DefaultServiceName = "temporal-lambda-worker";
private const string OTelExporterOtlpEndpointEnvironmentVariable =
"OTEL_EXPORTER_OTLP_ENDPOINT";

private const string OTelServiceNameEnvironmentVariable = "OTEL_SERVICE_NAME";
private const string LambdaFunctionNameEnvironmentVariable = "AWS_LAMBDA_FUNCTION_NAME";
private const string ServiceNameResourceAttribute = "service.name";

/// <summary>
/// Configure OpenTelemetry metrics and tracing with AWS Lambda defaults.
/// </summary>
/// <param name="config">Lambda worker configuration to mutate.</param>
/// <param name="options">Optional OpenTelemetry configuration.</param>
/// <remarks>
/// This creates an OTLP trace exporter and tracer provider, configures Core SDK metrics
/// through a Temporal runtime, adds the Temporal tracing interceptor, and registers a
/// per-invocation shutdown hook to force-flush traces and dispose the tracer provider
/// before the Lambda invocation ends.
/// Any existing <see cref="Temporalio.Client.TemporalConnectionOptions.Runtime" /> is
/// replaced.
/// </remarks>
public static void ApplyDefaults(
Comment thread
jmaeagle99 marked this conversation as resolved.
TemporalLambdaWorkerOptions config,
LambdaWorkerOpenTelemetryOptions? options = null)
{
if (config == null)
{
throw new ArgumentNullException(nameof(config));
}

var resolvedOptions = ResolveOptions(options);
#pragma warning disable CA2000 // The per-invocation shutdown hook owns provider disposal.
var tracerProvider = CreateTracerProvider(resolvedOptions);
#pragma warning restore CA2000

config.ClientOptions.Interceptors = AddTracingInterceptor(
config.ClientOptions.Interceptors);
config.ClientOptions.Runtime = CreateRuntime(resolvedOptions);
config.AddShutdownHook(
async cancellationToken =>
{
// CreateHandler runs configuration once per invocation, so this provider is
// invocation-scoped rather than warm-container-scoped. ForceFlush is the only
// bounded part of provider shutdown: Dispose is synchronous and has no
// cancellation-aware API. Run disposal after the flush attempt so exporting gets
// first use of the remaining Lambda deadline, while still releasing provider
// resources before the next warm invocation can accumulate another provider.
try
{
await ForceFlushAsync(
tracerProvider,
config.ShutdownDeadlineBuffer,
cancellationToken).ConfigureAwait(false);
}
finally
{
tracerProvider.Dispose();
}
});
}

/// <summary>
/// Resolve options using process environment variables.
/// </summary>
/// <param name="options">Options to resolve.</param>
/// <returns>Resolved options.</returns>
internal static ResolvedLambdaWorkerOpenTelemetryOptions ResolveOptions(
LambdaWorkerOpenTelemetryOptions? options = null)
{
options ??= new LambdaWorkerOpenTelemetryOptions();
if (options.MetricsExportInterval <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(
nameof(options),
"MetricsExportInterval must be greater than zero");
}

var serviceName = FirstNonEmpty(
options.ServiceName,
Environment.GetEnvironmentVariable(OTelServiceNameEnvironmentVariable),
Environment.GetEnvironmentVariable(LambdaFunctionNameEnvironmentVariable),
DefaultServiceName);
var collectorEndpoint = FirstNonEmpty(
options.CollectorEndpoint,
Environment.GetEnvironmentVariable(OTelExporterOtlpEndpointEnvironmentVariable),
DefaultCollectorEndpoint);

return new ResolvedLambdaWorkerOpenTelemetryOptions(
new Uri(collectorEndpoint),
serviceName,
options.MetricsExportInterval);
}

/// <summary>
/// Force-flush the tracer provider asynchronously.
/// </summary>
/// <param name="tracerProvider">Tracer provider to flush.</param>
/// <param name="shutdownDeadlineBuffer">Maximum time to wait for the flush.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task for the flush.</returns>
internal static async Task ForceFlushAsync(
TracerProvider tracerProvider,
TimeSpan shutdownDeadlineBuffer,
CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}

var flushTask = Task.Run(
() => tracerProvider.ForceFlush(ToTimeoutMilliseconds(shutdownDeadlineBuffer)));
try
{
await flushTask.WaitAsync(cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
flushTask.Forget();
}
}

private static string FirstNonEmpty(params string?[] values) =>
values.First(value => !string.IsNullOrEmpty(value))!;

private static TracerProvider CreateTracerProvider(
ResolvedLambdaWorkerOpenTelemetryOptions options) =>
Sdk.CreateTracerProviderBuilder().
AddXRayTraceId().
SetResourceBuilder(
ResourceBuilder.CreateDefault().AddService(options.ServiceName)).
AddSource(
TemporalOpenTelemetry.TracingInterceptor.ClientSource.Name,
TemporalOpenTelemetry.TracingInterceptor.WorkflowsSource.Name,
TemporalOpenTelemetry.TracingInterceptor.ActivitiesSource.Name,
TemporalOpenTelemetry.TracingInterceptor.NexusSource.Name).
AddOtlpExporter(exporterOptions =>
{
exporterOptions.Endpoint = options.CollectorEndpoint;
#pragma warning disable CS0618 // ADOT Lambda parity uses OTLP gRPC on localhost:4317.
exporterOptions.Protocol = OtlpExportProtocol.Grpc;
#pragma warning restore CS0618
}).
Build();

private static List<IClientInterceptor> AddTracingInterceptor(
IReadOnlyCollection<IClientInterceptor>? interceptors)
{
var newInterceptors = interceptors?.ToList() ?? new List<IClientInterceptor>();
newInterceptors.Add(new TemporalOpenTelemetry.TracingInterceptor());
return newInterceptors;
}

private static TemporalRuntime CreateRuntime(
ResolvedLambdaWorkerOpenTelemetryOptions options)
{
var openTelemetryOptions = new Temporalio.Runtime.OpenTelemetryOptions(
options.CollectorEndpoint)
{
MetricsExportInterval = options.MetricsExportInterval,
Protocol = OpenTelemetryProtocol.Grpc,
};
return new TemporalRuntime(new TemporalRuntimeOptions
{
Telemetry = new TelemetryOptions
{
Metrics = new MetricsOptions(openTelemetryOptions)
{
GlobalTags = new[]
{
new KeyValuePair<string, string>(
ServiceNameResourceAttribute,
options.ServiceName),
},
},
},
});
}

private static int ToTimeoutMilliseconds(TimeSpan timeout)
{
if (timeout <= TimeSpan.Zero)
{
return 0;
}
if (timeout.TotalMilliseconds >= int.MaxValue)
{
return int.MaxValue;
}
return (int)timeout.TotalMilliseconds;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;

namespace Temporalio.Extensions.Aws.Lambda.OpenTelemetry
{
/// <summary>
/// Options for <see cref="LambdaWorkerOpenTelemetry.ApplyDefaults"/>.
/// </summary>
public class LambdaWorkerOpenTelemetryOptions
{
/// <summary>
/// Gets or sets how often the Core SDK exports metrics to the collector.
/// </summary>
public TimeSpan MetricsExportInterval { get; set; } = TimeSpan.FromSeconds(10);

/// <summary>
/// Gets or sets the OpenTelemetry service name. If unset, this falls back to
/// OTEL_SERVICE_NAME, then AWS_LAMBDA_FUNCTION_NAME, then "temporal-lambda-worker".
/// </summary>
public string? ServiceName { get; set; }

/// <summary>
/// Gets or sets the OTLP collector endpoint. If unset, this falls back to
/// OTEL_EXPORTER_OTLP_ENDPOINT, then "http://localhost:4317".
/// </summary>
public string? CollectorEndpoint { get; set; }
}
}
Loading
Loading