Skip to content

Commit a2e27a2

Browse files
authored
Support log hooks (#253)
1 parent c80c712 commit a2e27a2

File tree

7 files changed

+173
-5
lines changed

7 files changed

+173
-5
lines changed

src/hooks/LogHookContext.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import * as types from '@azure/functions';
5+
import { ReadOnlyError } from '../errors';
6+
import { nonNullProp } from '../utils/nonNull';
7+
import { HookContext } from './HookContext';
8+
9+
export class LogHookContext extends HookContext implements types.LogHookContext {
10+
#init: types.LogHookContextInit;
11+
12+
constructor(init?: types.LogHookContextInit) {
13+
super(init);
14+
this.#init = init ?? {};
15+
this.#init.level ??= 'information';
16+
this.#init.message ??= 'unknown';
17+
this.#init.category ??= 'user';
18+
}
19+
20+
get level(): types.LogLevel {
21+
return nonNullProp(this.#init, 'level');
22+
}
23+
24+
set level(value: types.LogLevel) {
25+
this.#init.level = value;
26+
}
27+
28+
get message(): string {
29+
return nonNullProp(this.#init, 'message');
30+
}
31+
32+
set message(value: string) {
33+
this.#init.message = value;
34+
}
35+
36+
get category(): types.LogCategory {
37+
return nonNullProp(this.#init, 'category');
38+
}
39+
40+
set category(_value: types.LogCategory) {
41+
throw new ReadOnlyError('category');
42+
}
43+
44+
get invocationContext(): types.InvocationContext | undefined {
45+
return this.#init.invocationContext;
46+
}
47+
48+
set invocationContext(_value: types.InvocationContext | undefined) {
49+
throw new ReadOnlyError('invocationContext');
50+
}
51+
}

src/hooks/registerHook.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
import { AppStartHandler, AppTerminateHandler, PostInvocationHandler, PreInvocationHandler } from '@azure/functions';
4+
import {
5+
AppStartHandler,
6+
AppTerminateHandler,
7+
LogHookHandler,
8+
PostInvocationHandler,
9+
PreInvocationHandler,
10+
} from '@azure/functions';
511
import * as coreTypes from '@azure/functions-core';
12+
import { AzFuncSystemError, ensureErrorType } from '../errors';
613
import { Disposable } from '../utils/Disposable';
714
import { tryGetCoreApiLazy } from '../utils/tryGetCoreApiLazy';
815
import { AppStartContext } from './AppStartContext';
916
import { AppTerminateContext } from './AppTerminateContext';
17+
import { LogHookContext } from './LogHookContext';
1018
import { PostInvocationContext } from './PostInvocationContext';
1119
import { PreInvocationContext } from './PreInvocationContext';
1220

@@ -49,3 +57,18 @@ export function postInvocation(handler: PostInvocationHandler): Disposable {
4957
return handler(new PostInvocationContext(coreContext));
5058
});
5159
}
60+
61+
export function log(handler: LogHookHandler): Disposable {
62+
try {
63+
return registerHook('log', (coreContext) => {
64+
return handler(new LogHookContext(coreContext));
65+
});
66+
} catch (err) {
67+
const error = ensureErrorType(err);
68+
if (error.name === 'RangeError' && error.isAzureFunctionsSystemError) {
69+
throw new AzFuncSystemError(`Log hooks require Azure Functions Host v4.34 or higher.`);
70+
} else {
71+
throw err;
72+
}
73+
}
74+
}

types-core/index.d.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,15 @@ declare module '@azure/functions-core' {
5858
function registerHook(hookName: 'postInvocation', callback: PostInvocationCallback): Disposable;
5959
function registerHook(hookName: 'appStart', callback: AppStartCallback): Disposable;
6060
function registerHook(hookName: 'appTerminate', callback: AppTerminateCallback): Disposable;
61+
function registerHook(hookName: 'log', callback: LogHookCallback): Disposable;
6162
function registerHook(hookName: string, callback: HookCallback): Disposable;
6263

63-
type HookCallback = (context: HookContext) => void | Promise<void>;
64+
type HookCallback = (context: HookContext) => unknown;
6465
type PreInvocationCallback = (context: PreInvocationContext) => void | Promise<void>;
6566
type PostInvocationCallback = (context: PostInvocationContext) => void | Promise<void>;
6667
type AppStartCallback = (context: AppStartContext) => void | Promise<void>;
6768
type AppTerminateCallback = (context: AppTerminateContext) => void | Promise<void>;
69+
type LogHookCallback = (context: LogHookContext) => void;
6870

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

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

147149
type AppTerminateContext = HookContext;
148150

151+
interface LogHookContext extends HookContext {
152+
/**
153+
* If the log occurs during a function execution, the context object passed to the function handler.
154+
* Otherwise, undefined.
155+
*/
156+
readonly invocationContext?: unknown;
157+
158+
/**
159+
* 'system' if the log is generated by Azure Functions, 'user' if the log is generated by your own app.
160+
*/
161+
readonly category: RpcLogCategory;
162+
163+
/**
164+
* Changes to this value _will_ affect the resulting log, but only for user-generated logs.
165+
*/
166+
level: RpcLogLevel;
167+
168+
/**
169+
* Changes to this value _will_ affect the resulting log, but only for user-generated logs.
170+
*/
171+
message: string;
172+
}
173+
149174
/**
150175
* Represents a type which can release resources, such as event listening or a timer.
151176
*/

types/InvocationContext.d.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { CosmosDBInput, CosmosDBOutput } from './cosmosDB';
55
import { EventGridOutput, EventGridPartialEvent } from './eventGrid';
66
import { EventHubOutput } from './eventHub';
77
import { HttpOutput, HttpResponse } from './http';
8-
import { FunctionInput, FunctionOutput, FunctionTrigger } from './index';
8+
import { FunctionInput, FunctionOutput, FunctionTrigger, LogLevel } from './index';
99
import { ServiceBusQueueOutput, ServiceBusTopicOutput } from './serviceBus';
1010
import { SqlInput, SqlOutput } from './sql';
1111
import { StorageBlobInput, StorageBlobOutput, StorageQueueOutput } from './storage';
@@ -342,5 +342,3 @@ export interface InvocationContextInit {
342342
}
343343

344344
export type LogHandler = (level: LogLevel, ...args: unknown[]) => void;
345-
346-
export type LogLevel = 'trace' | 'debug' | 'information' | 'warning' | 'error' | 'critical' | 'none';

types/hooks/logHooks.d.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { LogLevel } from '../index';
5+
import { InvocationContext } from '../InvocationContext';
6+
import { HookContext, HookContextInit } from './HookContext';
7+
8+
/**
9+
* Handler for log hooks.
10+
*/
11+
export type LogHookHandler = (context: LogHookContext) => void;
12+
13+
/**
14+
* Context on a log
15+
*/
16+
export declare class LogHookContext extends HookContext {
17+
/**
18+
* For testing purposes only. This will always be constructed for you when run in the context of the Azure Functions runtime
19+
*/
20+
constructor(init?: LogHookContextInit);
21+
22+
/**
23+
* If the log occurs during a function execution, the context object passed to the function handler.
24+
* Otherwise, undefined.
25+
*/
26+
readonly invocationContext: InvocationContext | undefined;
27+
28+
/**
29+
* 'system' if the log is generated by Azure Functions, 'user' if the log is generated by your own app.
30+
*/
31+
readonly category: LogCategory;
32+
33+
/**
34+
* Changes to this value _will_ affect the resulting log, but only for user-generated logs.
35+
*/
36+
level: LogLevel;
37+
38+
/**
39+
* Changes to this value _will_ affect the resulting log, but only for user-generated logs.
40+
*/
41+
message: string;
42+
}
43+
44+
/**
45+
* Object passed to LogHookContext constructors.
46+
* For testing purposes only
47+
*/
48+
export interface LogHookContextInit extends HookContextInit {
49+
invocationContext?: InvocationContext;
50+
51+
level?: LogLevel;
52+
53+
category?: LogCategory;
54+
55+
message?: string;
56+
}
57+
58+
export type LogCategory = 'user' | 'system' | 'customMetric';

types/hooks/registerHook.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { Disposable } from '../index';
55
import { AppStartHandler, AppTerminateHandler } from './appHooks';
66
import { PostInvocationHandler, PreInvocationHandler } from './invocationHooks';
7+
import { LogHookHandler } from './logHooks';
78

89
/**
910
* Register a hook to be run at the start of your application
@@ -38,3 +39,12 @@ export function preInvocation(handler: PreInvocationHandler): Disposable;
3839
* @returns a `Disposable` object that can be used to unregister the hook
3940
*/
4041
export function postInvocation(handler: PostInvocationHandler): Disposable;
42+
43+
/**
44+
* PREVIEW: Register a hook to be run for each log.
45+
* This functionality requires Azure Functions Host v4.34+.
46+
*
47+
* @param handler the handler for the hook
48+
* @returns a `Disposable` object that can be used to unregister the hook
49+
*/
50+
export function log(handler: LogHookHandler): Disposable;

types/index.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export * from './generic';
1313
export * from './hooks/appHooks';
1414
export * from './hooks/HookContext';
1515
export * from './hooks/invocationHooks';
16+
export * from './hooks/logHooks';
1617
export * from './http';
1718
export * as input from './input';
1819
export * from './InvocationContext';
@@ -198,3 +199,5 @@ export declare class Disposable {
198199
*/
199200
dispose(): any;
200201
}
202+
203+
export type LogLevel = 'trace' | 'debug' | 'information' | 'warning' | 'error' | 'critical' | 'none';

0 commit comments

Comments
 (0)