Skip to content

Commit

Permalink
Support log hooks (#253)
Browse files Browse the repository at this point in the history
  • Loading branch information
ejizba committed May 2, 2024
1 parent c80c712 commit a2e27a2
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 5 deletions.
51 changes: 51 additions & 0 deletions src/hooks/LogHookContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import * as types from '@azure/functions';
import { ReadOnlyError } from '../errors';
import { nonNullProp } from '../utils/nonNull';
import { HookContext } from './HookContext';

export class LogHookContext extends HookContext implements types.LogHookContext {
#init: types.LogHookContextInit;

constructor(init?: types.LogHookContextInit) {
super(init);
this.#init = init ?? {};
this.#init.level ??= 'information';
this.#init.message ??= 'unknown';
this.#init.category ??= 'user';
}

get level(): types.LogLevel {
return nonNullProp(this.#init, 'level');
}

set level(value: types.LogLevel) {
this.#init.level = value;
}

get message(): string {
return nonNullProp(this.#init, 'message');
}

set message(value: string) {
this.#init.message = value;
}

get category(): types.LogCategory {
return nonNullProp(this.#init, 'category');
}

set category(_value: types.LogCategory) {
throw new ReadOnlyError('category');
}

get invocationContext(): types.InvocationContext | undefined {
return this.#init.invocationContext;
}

set invocationContext(_value: types.InvocationContext | undefined) {
throw new ReadOnlyError('invocationContext');
}
}
25 changes: 24 additions & 1 deletion src/hooks/registerHook.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import { AppStartHandler, AppTerminateHandler, PostInvocationHandler, PreInvocationHandler } from '@azure/functions';
import {
AppStartHandler,
AppTerminateHandler,
LogHookHandler,
PostInvocationHandler,
PreInvocationHandler,
} from '@azure/functions';
import * as coreTypes from '@azure/functions-core';
import { AzFuncSystemError, ensureErrorType } from '../errors';
import { Disposable } from '../utils/Disposable';
import { tryGetCoreApiLazy } from '../utils/tryGetCoreApiLazy';
import { AppStartContext } from './AppStartContext';
import { AppTerminateContext } from './AppTerminateContext';
import { LogHookContext } from './LogHookContext';
import { PostInvocationContext } from './PostInvocationContext';
import { PreInvocationContext } from './PreInvocationContext';

Expand Down Expand Up @@ -49,3 +57,18 @@ export function postInvocation(handler: PostInvocationHandler): Disposable {
return handler(new PostInvocationContext(coreContext));
});
}

export function log(handler: LogHookHandler): Disposable {
try {
return registerHook('log', (coreContext) => {
return handler(new LogHookContext(coreContext));
});
} catch (err) {
const error = ensureErrorType(err);
if (error.name === 'RangeError' && error.isAzureFunctionsSystemError) {
throw new AzFuncSystemError(`Log hooks require Azure Functions Host v4.34 or higher.`);
} else {
throw err;
}
}
}
27 changes: 26 additions & 1 deletion types-core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@ declare module '@azure/functions-core' {
function registerHook(hookName: 'postInvocation', callback: PostInvocationCallback): Disposable;
function registerHook(hookName: 'appStart', callback: AppStartCallback): Disposable;
function registerHook(hookName: 'appTerminate', callback: AppTerminateCallback): Disposable;
function registerHook(hookName: 'log', callback: LogHookCallback): Disposable;
function registerHook(hookName: string, callback: HookCallback): Disposable;

type HookCallback = (context: HookContext) => void | Promise<void>;
type HookCallback = (context: HookContext) => unknown;
type PreInvocationCallback = (context: PreInvocationContext) => void | Promise<void>;
type PostInvocationCallback = (context: PostInvocationContext) => void | Promise<void>;
type AppStartCallback = (context: AppStartContext) => void | Promise<void>;
type AppTerminateCallback = (context: AppTerminateContext) => void | Promise<void>;
type LogHookCallback = (context: LogHookContext) => void;

type HookData = { [key: string]: any };

Expand Down Expand Up @@ -146,6 +148,29 @@ declare module '@azure/functions-core' {

type AppTerminateContext = HookContext;

interface LogHookContext extends HookContext {
/**
* If the log occurs during a function execution, the context object passed to the function handler.
* Otherwise, undefined.
*/
readonly invocationContext?: unknown;

/**
* 'system' if the log is generated by Azure Functions, 'user' if the log is generated by your own app.
*/
readonly category: RpcLogCategory;

/**
* Changes to this value _will_ affect the resulting log, but only for user-generated logs.
*/
level: RpcLogLevel;

/**
* Changes to this value _will_ affect the resulting log, but only for user-generated logs.
*/
message: string;
}

/**
* Represents a type which can release resources, such as event listening or a timer.
*/
Expand Down
4 changes: 1 addition & 3 deletions types/InvocationContext.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CosmosDBInput, CosmosDBOutput } from './cosmosDB';
import { EventGridOutput, EventGridPartialEvent } from './eventGrid';
import { EventHubOutput } from './eventHub';
import { HttpOutput, HttpResponse } from './http';
import { FunctionInput, FunctionOutput, FunctionTrigger } from './index';
import { FunctionInput, FunctionOutput, FunctionTrigger, LogLevel } from './index';
import { ServiceBusQueueOutput, ServiceBusTopicOutput } from './serviceBus';
import { SqlInput, SqlOutput } from './sql';
import { StorageBlobInput, StorageBlobOutput, StorageQueueOutput } from './storage';
Expand Down Expand Up @@ -342,5 +342,3 @@ export interface InvocationContextInit {
}

export type LogHandler = (level: LogLevel, ...args: unknown[]) => void;

export type LogLevel = 'trace' | 'debug' | 'information' | 'warning' | 'error' | 'critical' | 'none';
58 changes: 58 additions & 0 deletions types/hooks/logHooks.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import { LogLevel } from '../index';
import { InvocationContext } from '../InvocationContext';
import { HookContext, HookContextInit } from './HookContext';

/**
* Handler for log hooks.
*/
export type LogHookHandler = (context: LogHookContext) => void;

/**
* Context on a log
*/
export declare class LogHookContext extends HookContext {
/**
* For testing purposes only. This will always be constructed for you when run in the context of the Azure Functions runtime
*/
constructor(init?: LogHookContextInit);

/**
* If the log occurs during a function execution, the context object passed to the function handler.
* Otherwise, undefined.
*/
readonly invocationContext: InvocationContext | undefined;

/**
* 'system' if the log is generated by Azure Functions, 'user' if the log is generated by your own app.
*/
readonly category: LogCategory;

/**
* Changes to this value _will_ affect the resulting log, but only for user-generated logs.
*/
level: LogLevel;

/**
* Changes to this value _will_ affect the resulting log, but only for user-generated logs.
*/
message: string;
}

/**
* Object passed to LogHookContext constructors.
* For testing purposes only
*/
export interface LogHookContextInit extends HookContextInit {
invocationContext?: InvocationContext;

level?: LogLevel;

category?: LogCategory;

message?: string;
}

export type LogCategory = 'user' | 'system' | 'customMetric';
10 changes: 10 additions & 0 deletions types/hooks/registerHook.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { Disposable } from '../index';
import { AppStartHandler, AppTerminateHandler } from './appHooks';
import { PostInvocationHandler, PreInvocationHandler } from './invocationHooks';
import { LogHookHandler } from './logHooks';

/**
* Register a hook to be run at the start of your application
Expand Down Expand Up @@ -38,3 +39,12 @@ export function preInvocation(handler: PreInvocationHandler): Disposable;
* @returns a `Disposable` object that can be used to unregister the hook
*/
export function postInvocation(handler: PostInvocationHandler): Disposable;

/**
* PREVIEW: Register a hook to be run for each log.
* This functionality requires Azure Functions Host v4.34+.
*
* @param handler the handler for the hook
* @returns a `Disposable` object that can be used to unregister the hook
*/
export function log(handler: LogHookHandler): Disposable;
3 changes: 3 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from './generic';
export * from './hooks/appHooks';
export * from './hooks/HookContext';
export * from './hooks/invocationHooks';
export * from './hooks/logHooks';
export * from './http';
export * as input from './input';
export * from './InvocationContext';
Expand Down Expand Up @@ -198,3 +199,5 @@ export declare class Disposable {
*/
dispose(): any;
}

export type LogLevel = 'trace' | 'debug' | 'information' | 'warning' | 'error' | 'critical' | 'none';

0 comments on commit a2e27a2

Please sign in to comment.