Skip to content

Commit 8bac003

Browse files
committed
feat: Add AzureLoggerAdapter for integrating Azure SDK logging
- Implemented AzureLoggerAdapter to integrate with Azure SDK logging infrastructure. - Created createAzureLogger function for customizable logging namespaces. - Added unit tests for AzureLoggerAdapter and createAzureLogger. feat: Introduce backoff utility for retry scenarios - Implemented ExponentialBackoff class with configurable options for retries. - Added utility functions sleep and withTimeout for handling delays and timeouts. - Created unit tests for ExponentialBackoff and related utilities. feat: Add ConsoleLogger and NoOpLogger implementations - Implemented ConsoleLogger for default logging to the console. - Added NoOpLogger for silent logging, useful in testing scenarios. - Created unit tests for both ConsoleLogger and NoOpLogger to ensure compliance with Logger interface. chore: Define Logger interface for Durable Task SDK - Created Logger interface to standardize logging across the SDK. - Added documentation for Logger interface and its default implementations.
1 parent a11af70 commit 8bac003

27 files changed

+1360
-225
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
!.vscode/launch.json
66
!.vscode/extensions.json
77
*.code-workspace
8-
8+
.DS_Store
99
### DotnetCore ###
1010
# .NET Core build folders
1111
bin/

examples/azure-managed-dts.ts

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import { DefaultAzureCredential } from "@azure/identity";
1414
import {
1515
createAzureManagedClient,
1616
createAzureManagedWorkerBuilder,
17+
// Logger types are re-exported for convenience
18+
// ConsoleLogger is used by default
19+
// Use createAzureLogger to integrate with @azure/logger
20+
createAzureLogger,
1721
} from "../extensions/durabletask-js-azuremanaged/build";
1822
import { ActivityContext } from "../src/task/context/activity-context";
1923
import { OrchestrationContext } from "../src/task/context/orchestration-context";
@@ -23,6 +27,10 @@ import { Task } from "../src/task/task";
2327

2428
// Wrap the entire code in an immediately-invoked async function
2529
(async () => {
30+
// Create a logger for this example
31+
// This uses Azure SDK's logging infrastructure - set AZURE_LOG_LEVEL=verbose to see all logs
32+
const logger = createAzureLogger("example");
33+
2634
// Configuration for Azure Managed DTS
2735
// These values should be set as environment variables
2836
const endpoint = process.env.AZURE_DTS_ENDPOINT;
@@ -31,31 +39,31 @@ import { Task } from "../src/task/task";
3139

3240
// Validate configuration
3341
if (!connectionString && (!endpoint || !taskHubName)) {
34-
console.error(
42+
logger.error(
3543
"Error: Either AZURE_DTS_CONNECTION_STRING or both AZURE_DTS_ENDPOINT and AZURE_DTS_TASKHUB must be set.",
3644
);
37-
console.log("\nUsage:");
38-
console.log(" Option 1: Create a .env file in the examples directory (recommended):");
39-
console.log(
45+
logger.info("\nUsage:");
46+
logger.info(" Option 1: Create a .env file in the examples directory (recommended):");
47+
logger.info(
4048
" AZURE_DTS_CONNECTION_STRING=Endpoint=https://myservice.durabletask.io;Authentication=DefaultAzure;TaskHub=myTaskHub",
4149
);
42-
console.log(" or");
43-
console.log(" AZURE_DTS_ENDPOINT=https://myservice.durabletask.io");
44-
console.log(" AZURE_DTS_TASKHUB=myTaskHub");
45-
console.log("\n Option 2: Set environment variables directly");
46-
console.log(" export AZURE_DTS_CONNECTION_STRING=...");
50+
logger.info(" or");
51+
logger.info(" AZURE_DTS_ENDPOINT=https://myservice.durabletask.io");
52+
logger.info(" AZURE_DTS_TASKHUB=myTaskHub");
53+
logger.info("\n Option 2: Set environment variables directly");
54+
logger.info(" export AZURE_DTS_CONNECTION_STRING=...");
4755
process.exit(1);
4856
}
4957

5058
// Define an activity function that greets a city
5159
const greetCity = async (_: ActivityContext, city: string): Promise<string> => {
52-
console.log(`Activity executing: greeting ${city}`);
60+
logger.info(`Activity executing: greeting ${city}`);
5361
return `Hello, ${city}!`;
5462
};
5563

5664
// Define an activity function that processes work items
5765
const processWorkItem = async (_: ActivityContext, item: string): Promise<number> => {
58-
console.log(`Activity executing: processing ${item}`);
66+
logger.info(`Activity executing: processing ${item}`);
5967
// Simulate some processing time
6068
await new Promise((resolve) => setTimeout(resolve, 500));
6169
return item.length;
@@ -98,7 +106,7 @@ import { Task } from "../src/task/task";
98106
try {
99107
// Create client and worker using connection string or explicit parameters
100108
if (connectionString) {
101-
console.log("Using connection string authentication...");
109+
logger.info("Using connection string authentication...");
102110
client = createAzureManagedClient(connectionString);
103111
worker = createAzureManagedWorkerBuilder(connectionString)
104112
.addOrchestrator(sequenceOrchestrator)
@@ -107,7 +115,7 @@ import { Task } from "../src/task/task";
107115
.addActivity(processWorkItem)
108116
.build();
109117
} else {
110-
console.log("Using DefaultAzureCredential authentication...");
118+
logger.info("Using DefaultAzureCredential authentication...");
111119
const credential = new DefaultAzureCredential();
112120
client = createAzureManagedClient(endpoint!, taskHubName!, credential);
113121
worker = createAzureManagedWorkerBuilder(endpoint!, taskHubName!, credential)
@@ -119,42 +127,42 @@ import { Task } from "../src/task/task";
119127
}
120128

121129
// Start the worker
122-
console.log("Starting worker...");
130+
logger.info("Starting worker...");
123131
await worker.start();
124-
console.log("Worker started successfully!");
132+
logger.info("Worker started successfully!");
125133

126134
// Run the sequence orchestrator
127-
console.log("\n--- Running Sequence Orchestrator ---");
135+
logger.info("\n--- Running Sequence Orchestrator ---");
128136
const sequenceId = await client.scheduleNewOrchestration(sequenceOrchestrator);
129-
console.log(`Orchestration scheduled with ID: ${sequenceId}`);
137+
logger.info(`Orchestration scheduled with ID: ${sequenceId}`);
130138

131139
const sequenceState = await client.waitForOrchestrationCompletion(sequenceId, undefined, 60);
132-
console.log(`Sequence orchestration completed!`);
133-
console.log(`Result: ${sequenceState?.serializedOutput}`);
140+
logger.info(`Sequence orchestration completed!`);
141+
logger.info(`Result: ${sequenceState?.serializedOutput}`);
134142

135143
// Run the fan-out/fan-in orchestrator
136-
console.log("\n--- Running Fan-Out/Fan-In Orchestrator ---");
144+
logger.info("\n--- Running Fan-Out/Fan-In Orchestrator ---");
137145
const fanOutId = await client.scheduleNewOrchestration(fanOutFanInOrchestrator);
138-
console.log(`Orchestration scheduled with ID: ${fanOutId}`);
146+
logger.info(`Orchestration scheduled with ID: ${fanOutId}`);
139147

140148
const fanOutState = await client.waitForOrchestrationCompletion(fanOutId, undefined, 60);
141-
console.log(`Fan-out/fan-in orchestration completed!`);
142-
console.log(`Result: ${fanOutState?.serializedOutput}`);
149+
logger.info(`Fan-out/fan-in orchestration completed!`);
150+
logger.info(`Result: ${fanOutState?.serializedOutput}`);
143151

144-
console.log("\n--- All orchestrations completed successfully! ---");
152+
logger.info("\n--- All orchestrations completed successfully! ---");
145153
} catch (error) {
146-
console.error("Error:", error);
154+
logger.error("Error:", error);
147155
process.exit(1);
148156
} finally {
149157
// Cleanup: stop worker and client
150-
console.log("\nStopping worker and client...");
158+
logger.info("\nStopping worker and client...");
151159
if (worker) {
152160
await worker.stop();
153161
}
154162
if (client) {
155163
await client.stop();
156164
}
157-
console.log("Cleanup complete.");
165+
logger.info("Cleanup complete.");
158166
process.exit(0);
159167
}
160168
})();

examples/azure-managed/index.ts

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import { DefaultAzureCredential } from "@azure/identity";
1414
import {
1515
createAzureManagedClient,
1616
createAzureManagedWorkerBuilder,
17+
// Logger types are re-exported for convenience
18+
// ConsoleLogger is used by default
19+
// Use createAzureLogger to integrate with @azure/logger
20+
createAzureLogger,
1721
} from "@microsoft/durabletask-js-azuremanaged";
1822
import { ActivityContext } from "@microsoft/durabletask-js/dist/task/context/activity-context";
1923
import { OrchestrationContext } from "@microsoft/durabletask-js/dist/task/context/orchestration-context";
@@ -23,6 +27,10 @@ import { Task } from "@microsoft/durabletask-js/dist/task/task";
2327

2428
// Wrap the entire code in an immediately-invoked async function
2529
(async () => {
30+
// Create a logger for this example
31+
// This uses Azure SDK's logging infrastructure - set AZURE_LOG_LEVEL=verbose to see all logs
32+
const logger = createAzureLogger("example");
33+
2634
// Configuration for Azure Managed DTS
2735
// These values should be set as environment variables
2836
const endpoint = process.env.AZURE_DTS_ENDPOINT;
@@ -31,31 +39,31 @@ import { Task } from "@microsoft/durabletask-js/dist/task/task";
3139

3240
// Validate configuration
3341
if (!connectionString && (!endpoint || !taskHubName)) {
34-
console.error(
42+
logger.error(
3543
"Error: Either AZURE_DTS_CONNECTION_STRING or both AZURE_DTS_ENDPOINT and AZURE_DTS_TASKHUB must be set.",
3644
);
37-
console.log("\nUsage:");
38-
console.log(" Option 1: Create a .env file in the examples directory (recommended):");
39-
console.log(
45+
logger.info("\nUsage:");
46+
logger.info(" Option 1: Create a .env file in the examples directory (recommended):");
47+
logger.info(
4048
" AZURE_DTS_CONNECTION_STRING=Endpoint=https://myservice.durabletask.io;Authentication=DefaultAzure;TaskHub=myTaskHub",
4149
);
42-
console.log(" or");
43-
console.log(" AZURE_DTS_ENDPOINT=https://myservice.durabletask.io");
44-
console.log(" AZURE_DTS_TASKHUB=myTaskHub");
45-
console.log("\n Option 2: Set environment variables directly");
46-
console.log(" export AZURE_DTS_CONNECTION_STRING=...");
50+
logger.info(" or");
51+
logger.info(" AZURE_DTS_ENDPOINT=https://myservice.durabletask.io");
52+
logger.info(" AZURE_DTS_TASKHUB=myTaskHub");
53+
logger.info("\n Option 2: Set environment variables directly");
54+
logger.info(" export AZURE_DTS_CONNECTION_STRING=...");
4755
process.exit(1);
4856
}
4957

5058
// Define an activity function that greets a city
5159
const greetCity = async (_: ActivityContext, city: string): Promise<string> => {
52-
console.log(`Activity executing: greeting ${city}`);
60+
logger.info(`Activity executing: greeting ${city}`);
5361
return `Hello, ${city}!`;
5462
};
5563

5664
// Define an activity function that processes work items
5765
const processWorkItem = async (_: ActivityContext, item: string): Promise<number> => {
58-
console.log(`Activity executing: processing ${item}`);
66+
logger.info(`Activity executing: processing ${item}`);
5967
// Simulate some processing time
6068
await new Promise((resolve) => setTimeout(resolve, 500));
6169
return item.length;
@@ -98,7 +106,7 @@ import { Task } from "@microsoft/durabletask-js/dist/task/task";
98106
try {
99107
// Create client and worker using connection string or explicit parameters
100108
if (connectionString) {
101-
console.log("Using connection string authentication...");
109+
logger.info("Using connection string authentication...");
102110
client = createAzureManagedClient(connectionString);
103111
worker = createAzureManagedWorkerBuilder(connectionString)
104112
.addOrchestrator(sequenceOrchestrator)
@@ -107,7 +115,7 @@ import { Task } from "@microsoft/durabletask-js/dist/task/task";
107115
.addActivity(processWorkItem)
108116
.build();
109117
} else {
110-
console.log("Using DefaultAzureCredential authentication...");
118+
logger.info("Using DefaultAzureCredential authentication...");
111119
const credential = new DefaultAzureCredential();
112120
client = createAzureManagedClient(endpoint!, taskHubName!, credential);
113121
worker = createAzureManagedWorkerBuilder(endpoint!, taskHubName!, credential)
@@ -119,42 +127,42 @@ import { Task } from "@microsoft/durabletask-js/dist/task/task";
119127
}
120128

121129
// Start the worker
122-
console.log("Starting worker...");
130+
logger.info("Starting worker...");
123131
await worker.start();
124-
console.log("Worker started successfully!");
132+
logger.info("Worker started successfully!");
125133

126134
// Run the sequence orchestrator
127-
console.log("\n--- Running Sequence Orchestrator ---");
135+
logger.info("\n--- Running Sequence Orchestrator ---");
128136
const sequenceId = await client.scheduleNewOrchestration(sequenceOrchestrator);
129-
console.log(`Orchestration scheduled with ID: ${sequenceId}`);
137+
logger.info(`Orchestration scheduled with ID: ${sequenceId}`);
130138

131139
const sequenceState = await client.waitForOrchestrationCompletion(sequenceId, undefined, 60);
132-
console.log(`Sequence orchestration completed!`);
133-
console.log(`Result: ${sequenceState?.serializedOutput}`);
140+
logger.info(`Sequence orchestration completed!`);
141+
logger.info(`Result: ${sequenceState?.serializedOutput}`);
134142

135143
// Run the fan-out/fan-in orchestrator
136-
console.log("\n--- Running Fan-Out/Fan-In Orchestrator ---");
144+
logger.info("\n--- Running Fan-Out/Fan-In Orchestrator ---");
137145
const fanOutId = await client.scheduleNewOrchestration(fanOutFanInOrchestrator);
138-
console.log(`Orchestration scheduled with ID: ${fanOutId}`);
146+
logger.info(`Orchestration scheduled with ID: ${fanOutId}`);
139147

140148
const fanOutState = await client.waitForOrchestrationCompletion(fanOutId, undefined, 60);
141-
console.log(`Fan-out/fan-in orchestration completed!`);
142-
console.log(`Result: ${fanOutState?.serializedOutput}`);
149+
logger.info(`Fan-out/fan-in orchestration completed!`);
150+
logger.info(`Result: ${fanOutState?.serializedOutput}`);
143151

144-
console.log("\n--- All orchestrations completed successfully! ---");
152+
logger.info("\n--- All orchestrations completed successfully! ---");
145153
} catch (error) {
146-
console.error("Error:", error);
154+
logger.error("Error:", error);
147155
process.exit(1);
148156
} finally {
149157
// Cleanup: stop worker and client
150-
console.log("\nStopping worker and client...");
158+
logger.info("\nStopping worker and client...");
151159
if (worker) {
152160
await worker.stop();
153161
}
154162
if (client) {
155163
await client.stop();
156164
}
157-
console.log("Cleanup complete.");
165+
logger.info("Cleanup complete.");
158166
process.exit(0);
159167
}
160168
})();

examples/hello-world/activity-sequence.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,40 @@ import {
77
ActivityContext,
88
OrchestrationContext,
99
TOrchestrator,
10+
// Logger types for custom logging
11+
// ConsoleLogger (default) - logs to console
12+
// NoOpLogger - silent mode, useful for testing
13+
// You can also implement your own Logger interface
14+
ConsoleLogger,
1015
} from "@microsoft/durabletask-js";
1116

1217
// Wrap the entire code in an immediately-invoked async function
1318
(async () => {
1419
// Update the gRPC client and worker to use a local address and port
1520
const grpcServerAddress = "localhost:4001";
16-
const taskHubClient: TaskHubGrpcClient = new TaskHubGrpcClient(grpcServerAddress);
17-
const taskHubWorker: TaskHubGrpcWorker = new TaskHubGrpcWorker(grpcServerAddress);
21+
22+
// Optional: Create a custom logger (defaults to ConsoleLogger if not provided)
23+
// You can implement your own Logger interface to integrate with Winston, Pino, etc.
24+
const logger = new ConsoleLogger();
25+
26+
// Pass the logger as the 6th parameter (after metadataGenerator)
27+
// Parameters: hostAddress, options, useTLS, credentials, metadataGenerator, logger
28+
const taskHubClient: TaskHubGrpcClient = new TaskHubGrpcClient(
29+
grpcServerAddress,
30+
undefined,
31+
undefined,
32+
undefined,
33+
undefined,
34+
logger,
35+
);
36+
const taskHubWorker: TaskHubGrpcWorker = new TaskHubGrpcWorker(
37+
grpcServerAddress,
38+
undefined,
39+
undefined,
40+
undefined,
41+
undefined,
42+
logger,
43+
);
1844

1945
const hello = async (_: ActivityContext, name: string) => {
2046
return `Hello ${name}!`;
@@ -39,22 +65,22 @@ import {
3965
// Wrap the worker startup in a try-catch block to handle any errors during startup
4066
try {
4167
await taskHubWorker.start();
42-
console.log("Worker started successfully");
68+
logger.info("Worker started successfully");
4369
} catch (error) {
44-
console.error("Error starting worker:", error);
70+
logger.error("Error starting worker:", error);
4571
}
4672

4773
// Schedule a new orchestration
4874
try {
4975
const id = await taskHubClient.scheduleNewOrchestration(sequence);
50-
console.log(`Orchestration scheduled with ID: ${id}`);
76+
logger.info(`Orchestration scheduled with ID: ${id}`);
5177

5278
// Wait for orchestration completion
5379
const state = await taskHubClient.waitForOrchestrationCompletion(id, undefined, 30);
5480

55-
console.log(`Orchestration completed! Result: ${state?.serializedOutput}`);
81+
logger.info(`Orchestration completed! Result: ${state?.serializedOutput}`);
5682
} catch (error) {
57-
console.error("Error scheduling or waiting for orchestration:", error);
83+
logger.error("Error scheduling or waiting for orchestration:", error);
5884
}
5985

6086
// stop worker and client

0 commit comments

Comments
 (0)