Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support log hooks #253

Merged
merged 2 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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');
}
}
15 changes: 14 additions & 1 deletion src/hooks/registerHook.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
// 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 { 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 +56,9 @@ export function postInvocation(handler: PostInvocationHandler): Disposable {
return handler(new PostInvocationContext(coreContext));
});
}

export function log(handler: LogHookHandler): Disposable {
return registerHook('log', (coreContext) => {
return handler(new LogHookContext(coreContext));
});
}
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';
Loading