azureloggingazure-functionsazure-durable-functions

TypeScript - Azure Durable functions: Use Context inside ActivityHandler


TLDR; How do I get context.log/warn/error inside an ActivityHandler???

Full explanation: I'm working with Azure Functions, TypeScript, Program Model V4. I have a durable function to do function chaining. The OrchestrationHandler has a context. But not the kind I need and not in the place I need it. The orchestrator calls activities like this:

const moveToMainActivityName = 'moveToMainContainerActivity';    
yield context.df.callActivity(moveToMainActivityName, itemsToProcess);

The Activity is defined like this:

const moveToMainContainerActivity: ActivityHandler = async (records: any[])=>{
  //do stuff
    context.error(`Failed to process items: ${error.message}`);
  //other stuff
};

And is registered like this:

df.app.activity(moveToMainActivityName, { handler: moveToMainContainerActivity });

My troubles start here, I can't get the context object inside this handler. If I add it to the signature of the activity handler:

const moveToMainContainerActivity: ActivityHandler = async (context, records: any[]) 

It will complain that that signature is not compatible with the type ActivityHandler. If I change the type to be like this:

const moveToMainContainerActivity: (context, records: any[]) => Promise<number> = async (context, records: any[])

the line where it registers will complain that that is not a valid handler:

df.app.activity(moveToMainActivityName, { handler: moveToMainContainerActivity });

I have tried variations of these kinds of refactors but no luck. Adding the extraInputs ActivityOption also does not result in runnable code. I asked Bard/GPT/Jetbrains AI assistant. But they are all very confused and suggest non-existing things. Googling like the olden days also has not provided any workable examples. I'm afraid my combination of using typescript for Azure functions, and the new programming model put me in this position.

I am also aware that trying to use the context to output errors/logs/warns is maybe not even possible. Or not at all best practice. If so, what would be the correct approach to get behavior similar to what I do in my other Azure functions with context.warn etc.

So please stack overflow, and prove humans are unbeatable when looking for programming advice.


Solution

  • This worked for me.

    I have created a sample durable function in model v4 and added context: InvocationContext in Activity Handler

    durableHello1.ts:

    import { app, HttpHandler, HttpRequest, HttpResponse, InvocationContext } from '@azure/functions';
    import * as df from 'durable-functions';
    import { ActivityHandler, OrchestrationContext, OrchestrationHandler } from 'durable-functions';
    
    const activityName = 'durableHello1';
    
    const durableHello1Orchestrator: OrchestrationHandler = function* (context: OrchestrationContext) {
        const outputs = [];
        outputs.push(yield context.df.callActivity(activityName, 'Tokyo'));
    
        return outputs;
    };
    df.app.orchestration('durableHello1Orchestrator', durableHello1Orchestrator);
    
    const durableHello1: ActivityHandler = (input: string,context:InvocationContext): string => {
        context.error("this is error log inside activity");
        context.warn("this is warn log inside acitivity");
        return `Hello, ${input}`;
    };
    df.app.activity(activityName, { handler: durableHello1 });
    
    const durableHello1HttpStart: HttpHandler = async (request: HttpRequest, context: InvocationContext): Promise<HttpResponse> => {
        const client = df.getClient(context);
        const body: unknown = await request.text();
        const instanceId: string = await client.startNew(request.params.orchestratorName, { input: body });
    
        context.log(`Started orchestration with ID = '${instanceId}'.`);
    
        return client.createCheckStatusResponse(request, instanceId);
    };
    
    app.http('durableHello1HttpStart', {
        route: 'orchestrators/{orchestratorName}',
        extraInputs: [df.input.durableClient()],
        handler: durableHello1HttpStart,
    });
    

    package.json:

    {
      "name": "nodejs",
      "version": "1.0.0",
      "description": "",
      "scripts": {
        "build": "tsc",
        "watch": "tsc -w",
        "clean": "rimraf dist",
        "prestart": "npm run clean && npm run build",
        "start": "func start",
        "test": "echo \"No tests yet...\""
      },
      "dependencies": {
        "@azure/functions": "^4.0.0",
        "durable-functions": "^3.0.0"
      },
      "devDependencies": {
        "@types/node": "^18.x",
        "rimraf": "^5.0.0",
        "typescript": "^4.0.0"
      },
      "main": "dist/src/functions/*.js"
    }
    

    OUTPUT:

    enter image description here

    enter image description here

    enter image description here