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 @@ -171,6 +171,10 @@ jobs:
src/Temporalio/bin/Release/*.snupkg
src/Temporalio.Extensions.DiagnosticSource/bin/Release/*.nupkg
src/Temporalio.Extensions.DiagnosticSource/bin/Release/*.snupkg
src/Temporalio.Extensions.Aws.Lambda/bin/Release/*.nupkg

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.

Please alphabetize

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.Hosting/bin/Release/*.nupkg
src/Temporalio.Extensions.Hosting/bin/Release/*.snupkg
src/Temporalio.Extensions.OpenTelemetry/bin/Release/*.nupkg
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="17.4.33" />
<PackageVersion Include="NexusRpc" Version="0.3.0" />
<PackageVersion Include="OpenTelemetry" Version="1.15.3" />
<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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,26 @@ var client = await TemporalClient.ConnectAsync(new("my-namespace.a1b2c.tmprl.clo
});
```

#### Client Configuration From Environment

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.

Please separate this out from this change and open a separate PR


Client connection options can be loaded from a `temporal.toml` file and environment variables:

```csharp
using Temporalio.Client;
using Temporalio.Common.EnvConfig;

var client = await TemporalClient.ConnectAsync(
ClientEnvConfig.LoadClientConnectOptions());
```

By default, the loader checks `TEMPORAL_CONFIG_FILE`; if unset, it looks for `temporal.toml` in the
user config directory under `temporalio`. The selected profile is `TEMPORAL_PROFILE`, or `default`
when unset. Environment variables such as `TEMPORAL_ADDRESS`, `TEMPORAL_NAMESPACE`,
`TEMPORAL_API_KEY`, TLS certificate/key settings, and `TEMPORAL_GRPC_META_*` override file values.

Use `ClientEnvConfig.ProfileLoadOptions` to choose a profile, provide an explicit config source, or
disable file or environment loading.

#### Client Dependency Injection

To create clients for use with dependency injection, see the
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 @@ -16,5 +16,7 @@ Commonly used namespaces:
Extensions:

* [Temporalio.Extensions.DiagnosticSource](/api/Temporalio.Extensions.DiagnosticSource.html)
* [Temporalio.Extensions.Aws.Lambda](/api/Temporalio.Extensions.Aws.Lambda.html)

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.

Please alphabetize

* [Temporalio.Extensions.Aws.Lambda.OpenTelemetry](/api/Temporalio.Extensions.Aws.Lambda.OpenTelemetry.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 @@ -6,6 +6,8 @@
"files": [
"Temporalio/*.csproj",
"Temporalio.Extensions.DiagnosticSource/*.csproj",
"Temporalio.Extensions.Aws.Lambda/*.csproj",

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.

Please alphabetize

"Temporalio.Extensions.Aws.Lambda.OpenTelemetry/*.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,204 @@
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 before the Lambda invocation ends.
/// </remarks>
public static void ApplyDefaults(
LambdaWorkerConfig config,
LambdaWorkerOpenTelemetryOptions? options = null)
{
if (config == null)
{
throw new ArgumentNullException(nameof(config));
}

var resolvedOptions = ResolveOptions(options);
#pragma warning disable CA2000 // Provider is intentionally retained for Lambda warm invocations.
var tracerProvider = CreateTracerProvider(resolvedOptions);
#pragma warning restore CA2000

config.ClientOptions.Interceptors = AddTracingInterceptor(
config.ClientOptions.Interceptors);
config.ClientOptions.Runtime = CreateRuntime(resolvedOptions);
config.ShutdownHooks.Add(
cancellationToken => ForceFlushAsync(
tracerProvider,
config.ShutdownDeadlineBuffer,
cancellationToken));
}

/// <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)));
if (flushTask == await Task.WhenAny(
flushTask,
Task.Delay(Timeout.Infinite, cancellationToken)).ConfigureAwait(false))
{
await flushTask.ConfigureAwait(false);
}
else
{
ObserveTaskException(flushTask);
}
}

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

private static void ObserveTaskException(Task task) =>
_ = task.ContinueWith(
completedTask => _ = completedTask.Exception,
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);

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