Skip to content

Commit 684a843

Browse files
authored
Add support for OpenTelemetry in worker (#1058)
* OpenTelemetry support in worker (#1049) * Detect if OpenTelemetry module is present and environment variable is set * Pre-instrument invocations with a span using Invocation ID + parent span ID * Forward all user logs to the module to be sent to OTel endpoint (cherry picked from commit f0c7eec)
1 parent 468617c commit 684a843

16 files changed

+657
-74
lines changed

protobuf/src/proto/FunctionRpc.proto

Lines changed: 122 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ message StreamingMessage {
3232
WorkerInitRequest worker_init_request = 17;
3333
// Worker responds after initializing with its capabilities & status
3434
WorkerInitResponse worker_init_response = 16;
35-
35+
36+
// MESSAGE NOT USED
3637
// Worker periodically sends empty heartbeat message to host
3738
WorkerHeartbeat worker_heartbeat = 15;
3839

@@ -85,6 +86,13 @@ message StreamingMessage {
8586

8687
// Host gets the list of function load responses
8788
FunctionLoadResponseCollection function_load_response_collection = 32;
89+
90+
// Host sends required metadata to worker to warmup the worker
91+
WorkerWarmupRequest worker_warmup_request = 33;
92+
93+
// Worker responds after warming up with the warmup result
94+
WorkerWarmupResponse worker_warmup_response = 34;
95+
8896
}
8997
}
9098

@@ -120,7 +128,7 @@ message WorkerInitRequest {
120128

121129
// Worker responds with the result of initializing itself
122130
message WorkerInitResponse {
123-
// NOT USED
131+
// PROPERTY NOT USED
124132
// TODO: Remove from protobuf during next breaking change release
125133
string worker_version = 1;
126134

@@ -173,7 +181,7 @@ message StatusResult {
173181
repeated RpcLog logs = 3;
174182
}
175183

176-
// NOT USED
184+
// MESSAGE NOT USED
177185
// TODO: Remove from protobuf during next breaking change release
178186
message WorkerHeartbeat {}
179187

@@ -187,7 +195,7 @@ message WorkerTerminate {
187195
message FileChangeEventRequest {
188196
// Types of File change operations (See link for more info: https://msdn.microsoft.com/en-us/library/t6xf43e0(v=vs.110).aspx)
189197
enum Type {
190-
Unknown = 0;
198+
Unknown = 0;
191199
Created = 1;
192200
Deleted = 2;
193201
Changed = 4;
@@ -237,8 +245,26 @@ message FunctionEnvironmentReloadRequest {
237245
}
238246

239247
message FunctionEnvironmentReloadResponse {
248+
enum CapabilitiesUpdateStrategy {
249+
// overwrites existing values and appends new ones
250+
// ex. worker init: {A: foo, B: bar} + env reload: {A:foo, B: foo, C: foo} -> {A: foo, B: foo, C: foo}
251+
merge = 0;
252+
// existing capabilities are cleared and new capabilities are applied
253+
// ex. worker init: {A: foo, B: bar} + env reload: {A:foo, C: foo} -> {A: foo, C: foo}
254+
replace = 1;
255+
}
256+
// After specialization, worker sends capabilities & metadata.
257+
// Worker metadata captured for telemetry purposes
258+
WorkerMetadata worker_metadata = 1;
259+
260+
// A map of worker supported features/capabilities
261+
map<string, string> capabilities = 2;
262+
240263
// Status of the response
241264
StatusResult result = 3;
265+
266+
// If no strategy is defined, the host will default to merge
267+
CapabilitiesUpdateStrategy capabilities_update_strategy = 4;
242268
}
243269

244270
// Tell the out-of-proc worker to close any shared memory maps it allocated for given invocation
@@ -322,10 +348,13 @@ message RpcFunctionMetadata {
322348
// A flag indicating if managed dependency is enabled or not
323349
bool managed_dependency_enabled = 14;
324350

351+
// The optional function execution retry strategy to use on invocation failures.
352+
RpcRetryOptions retry_options = 15;
353+
325354
// Properties for function metadata
326355
// They're usually specific to a worker and largely passed along to the controller API for use
327356
// outside the host
328-
map<string,string> Properties = 16;
357+
map<string,string> properties = 16;
329358
}
330359

331360
// Host tells worker it is ready to receive metadata
@@ -369,14 +398,14 @@ message InvocationRequest {
369398

370399
// Host sends ActivityId, traceStateString and Tags from host
371400
message RpcTraceContext {
372-
// This corresponds to Activity.Current?.Id
373-
string trace_parent = 1;
401+
// This corresponds to Activity.Current?.Id
402+
string trace_parent = 1;
374403

375-
// This corresponds to Activity.Current?.TraceStateString
376-
string trace_state = 2;
404+
// This corresponds to Activity.Current?.TraceStateString
405+
string trace_state = 2;
377406

378-
// This corresponds to Activity.Current?.Tags
379-
map<string, string> attributes = 3;
407+
// This corresponds to Activity.Current?.Tags
408+
map<string, string> attributes = 3;
380409
}
381410

382411
// Host sends retry context for a function invocation
@@ -396,8 +425,8 @@ message InvocationCancel {
396425
// Unique id for invocation
397426
string invocation_id = 2;
398427

399-
// Time period before force shutdown
400-
google.protobuf.Duration grace_period = 1; // could also use absolute time
428+
// PROPERTY NOT USED
429+
google.protobuf.Duration grace_period = 1;
401430
}
402431

403432
// Worker responds with status of Invocation
@@ -415,6 +444,15 @@ message InvocationResponse {
415444
StatusResult result = 3;
416445
}
417446

447+
message WorkerWarmupRequest {
448+
// Full path of worker.config.json location
449+
string worker_directory = 1;
450+
}
451+
452+
message WorkerWarmupResponse {
453+
StatusResult result = 1;
454+
}
455+
418456
// Used to encapsulate data which could be a variety of types
419457
message TypedData {
420458
oneof data {
@@ -429,6 +467,8 @@ message TypedData {
429467
CollectionString collection_string = 9;
430468
CollectionDouble collection_double = 10;
431469
CollectionSInt64 collection_sint64 = 11;
470+
ModelBindingData model_binding_data = 12;
471+
CollectionModelBindingData collection_model_binding_data = 13;
432472
}
433473
}
434474

@@ -496,20 +536,20 @@ message ParameterBinding {
496536

497537
// Used to describe a given binding on load
498538
message BindingInfo {
499-
// Indicates whether it is an input or output binding (or a fancy inout binding)
500-
enum Direction {
501-
in = 0;
502-
out = 1;
503-
inout = 2;
504-
}
505-
506-
// Indicates the type of the data for the binding
507-
enum DataType {
508-
undefined = 0;
509-
string = 1;
510-
binary = 2;
511-
stream = 3;
512-
}
539+
// Indicates whether it is an input or output binding (or a fancy inout binding)
540+
enum Direction {
541+
in = 0;
542+
out = 1;
543+
inout = 2;
544+
}
545+
546+
// Indicates the type of the data for the binding
547+
enum DataType {
548+
undefined = 0;
549+
string = 1;
550+
binary = 2;
551+
stream = 3;
552+
}
513553

514554
// Type of binding (e.g. HttpTrigger)
515555
string type = 2;
@@ -518,6 +558,9 @@ message BindingInfo {
518558
Direction direction = 3;
519559

520560
DataType data_type = 4;
561+
562+
// Properties for binding metadata
563+
map<string, string> properties = 5;
521564
}
522565

523566
// Used to send logs back to the Host
@@ -582,13 +625,13 @@ message RpcException {
582625
// Textual message describing the exception
583626
string message = 2;
584627

585-
// Worker specifies whether exception is a user exception,
586-
// for purpose of application insights logging. Defaults to false.
628+
// Worker specifies whether exception is a user exception,
629+
// for purpose of application insights logging. Defaults to false.
587630
bool is_user_exception = 4;
588631

589632
// Type of exception. If it's a user exception, the type is passed along to app insights.
590633
// Otherwise, it's ignored for now.
591-
string type = 5;
634+
string type = 5;
592635
}
593636

594637
// Http cookie type. Note that only name and value are used for Http requests
@@ -647,3 +690,52 @@ message RpcHttp {
647690
map<string,NullableString> nullable_params = 21;
648691
map<string,NullableString> nullable_query = 22;
649692
}
693+
694+
// Message representing Microsoft.Azure.WebJobs.ParameterBindingData
695+
// Used for hydrating SDK-type bindings in out-of-proc workers
696+
message ModelBindingData
697+
{
698+
// The version of the binding data content
699+
string version = 1;
700+
701+
// The extension source of the binding data
702+
string source = 2;
703+
704+
// The content type of the binding data content
705+
string content_type = 3;
706+
707+
// The binding data content
708+
bytes content = 4;
709+
}
710+
711+
// Used to encapsulate collection model_binding_data
712+
message CollectionModelBindingData {
713+
repeated ModelBindingData model_binding_data = 1;
714+
}
715+
716+
// Retry policy which the worker sends the host when the worker indexes
717+
// a function.
718+
message RpcRetryOptions
719+
{
720+
// The retry strategy to use. Valid values are fixed delay or exponential backoff.
721+
enum RetryStrategy
722+
{
723+
exponential_backoff = 0;
724+
fixed_delay = 1;
725+
}
726+
727+
// The maximum number of retries allowed per function execution.
728+
// -1 means to retry indefinitely.
729+
int32 max_retry_count = 2;
730+
731+
// The delay that's used between retries when you're using a fixed delay strategy.
732+
google.protobuf.Duration delay_interval = 3;
733+
734+
// The minimum retry delay when you're using an exponential backoff strategy
735+
google.protobuf.Duration minimum_interval = 4;
736+
737+
// The maximum retry delay when you're using an exponential backoff strategy
738+
google.protobuf.Duration maximum_interval = 5;
739+
740+
RetryStrategy retry_strategy = 6;
741+
}

src/DurableSDK/PowerShellServices.cs

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@ namespace Microsoft.Azure.Functions.PowerShellWorker.Durable
99
using System.Collections.ObjectModel;
1010
using System.Linq;
1111
using System.Management.Automation;
12-
using System.Reflection.Metadata;
1312
using Microsoft.Azure.Functions.PowerShellWorker.PowerShell;
1413
using Microsoft.Azure.Functions.PowerShellWorker.Utility;
1514
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
1615
using Newtonsoft.Json;
17-
using LogLevel = WebJobs.Script.Grpc.Messages.RpcLog.Types.Level;
1816

1917
internal class PowerShellServices : IPowerShellServices
2018
{
@@ -43,36 +41,7 @@ public PowerShellServices(PowerShell pwsh, ILogger logger)
4341

4442
public bool isExternalDurableSdkLoaded()
4543
{
46-
// Search for the external DF SDK in the current session
47-
var matchingModules = _pwsh.AddCommand(Utils.GetModuleCmdletInfo)
48-
.AddParameter("FullyQualifiedName", Utils.ExternalDurableSdkName)
49-
.InvokeAndClearCommands<PSModuleInfo>();
50-
51-
// If we get at least one result, we know the external SDK was imported
52-
var numCandidates = matchingModules.Count();
53-
var isModuleInCurrentSession = numCandidates > 0;
54-
55-
if (isModuleInCurrentSession)
56-
{
57-
var candidatesInfo = matchingModules.Select(module => string.Format(
58-
PowerShellWorkerStrings.FoundExternalDurableSdkInSession, module.Name, module.Version, module.Path));
59-
var externalSDKModuleInfo = string.Join('\n', candidatesInfo);
60-
61-
if (numCandidates > 1)
62-
{
63-
// If there's more than 1 result, there may be runtime conflicts
64-
// warn user of potential conflicts
65-
_logger.Log(isUserOnlyLog: false, LogLevel.Warning, String.Format(
66-
PowerShellWorkerStrings.MultipleExternalSDKsInSession,
67-
numCandidates, Utils.ExternalDurableSdkName, externalSDKModuleInfo));
68-
}
69-
else
70-
{
71-
// a single external SDK is in session. Report its metadata
72-
_logger.Log(isUserOnlyLog: false, LogLevel.Trace, externalSDKModuleInfo);
73-
}
74-
}
75-
return isModuleInCurrentSession;
44+
return PowerShellModuleDetector.IsPowerShellModuleLoaded(_pwsh, _logger, Utils.ExternalDurableSdkName);
7645
}
7746

7847
public void EnableExternalDurableSDK()

src/Logging/LoggingEventHandler.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace Microsoft.Azure.Functions.PowerShellWorker.Utility
5+
{
6+
internal class LoggingEventHandler
7+
{
8+
private Action<string, string, Exception> _eventHandler = (a, b, c) => { };
9+
10+
public void Subscribe(Action<string, string, Exception> handler)
11+
{
12+
_eventHandler = handler;
13+
}
14+
15+
public void LogToHandlers(string level, string message, Exception exception = null)
16+
{
17+
_eventHandler(level, message, exception);
18+
}
19+
}
20+
}

src/Logging/RpcLogger.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ internal class RpcLogger : ILogger
1818
private readonly MessagingStream _msgStream;
1919
private string _invocationId;
2020
private string _requestId;
21+
public LoggingEventHandler outputLogHandler = new LoggingEventHandler();
2122

2223
internal RpcLogger(MessagingStream msgStream)
2324
{
@@ -55,6 +56,8 @@ public void Log(bool isUserOnlyLog, LogLevel logLevel, string message, Exception
5556
};
5657

5758
_msgStream.Write(logMessage);
59+
60+
outputLogHandler.LogToHandlers(logLevel.ToString(), message, exception);
5861
}
5962
else
6063
{
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Microsoft.Azure.Functions.PowerShellWorker.OpenTelemetry
8+
{
9+
internal interface IPowerShellServicesForOpenTelemetry
10+
{
11+
bool? IsModuleLoaded();
12+
void AddStartOpenTelemetryInvocationCommand(OpenTelemetryInvocationContext otelContext);
13+
void StopOpenTelemetryInvocation(OpenTelemetryInvocationContext otelContext, bool invokeCommands);
14+
void StartFunctionsLoggingListener(bool invokeCommands);
15+
}
16+
}

0 commit comments

Comments
 (0)