-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathdecorators.ts
More file actions
95 lines (85 loc) · 3.41 KB
/
decorators.ts
File metadata and controls
95 lines (85 loc) · 3.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import type { LogContext } from "./types";
import type { SdkLogger } from "./SdkLogger";
/**
* Function type for building context from method arguments
*/
export type ContextBuilder<T = unknown> = (thisArg: T, args: unknown[]) => LogContext;
/**
* Options for the WithLoggerContext decorator
*/
export interface WithLoggerContextOptions<T = unknown> {
/**
* The logger instance to use for logging
*/
logger: SdkLogger;
/**
* The method name to use for logging context (required to avoid minification issues)
*/
methodName: string;
/**
* Optional function to build additional context from the method's this and arguments
*/
buildContext?: ContextBuilder<T>;
}
/**
* Decorator for public SDK methods that wraps them with logger execution context.
*
* This decorator:
* - Wraps the method execution with logger.withExecutionContext()
* - Automatically generates an execution ID for tracing
* - Checks if there's already an active execution context to avoid stepping over existing context
* - Properly handles both sync and async methods
*
* @example
* ```typescript
* class Wallet {
* @WithLoggerContext({
* logger: walletsLogger,
* methodName: "wallet.send",
* buildContext(thisArg, [to, token, amount]) {
* return { chain: thisArg.chain, address: thisArg.address, to, token, amount };
* },
* })
* public async send(to: string, token: string, amount: string): Promise<Transaction> {
* // Method body - logs will automatically include execution_id and method context
* walletsLogger.info("wallet.send.start");
* // ...
* }
* }
* ```
*/
export function WithLoggerContext<TThis = unknown>(options: WithLoggerContextOptions<TThis>): MethodDecorator {
return function (target, propertyKey, descriptor) {
const original = descriptor.value as ((...args: unknown[]) => unknown) | undefined;
if (original == null) {
return descriptor;
}
const wrapped = function (this: TThis, ...args: unknown[]): unknown {
const ctx = options.buildContext ? options.buildContext(this, args) : {};
// Delegate execution lifecycle to logger.withExecutionContext
// Note: We intentionally do NOT log method arguments or return values here.
// Method args/results may contain sensitive data (JWTs, API keys, auth tokens).
// Each method is responsible for its own curated log statements with safe fields.
return options.logger.withExecutionContext(options.methodName, ctx, () => {
let result: unknown;
try {
result = original.apply(this, args);
} catch (error) {
options.logger.error(`${options.methodName} threw an error`, { error });
throw error;
}
// Handle async functions
if (result instanceof Promise) {
return result.catch((error) => {
options.logger.error(`${options.methodName} threw an error`, { error });
throw error;
});
}
return result;
});
};
// The cast confines unsafety to the decorator implementation
descriptor.value = wrapped as typeof descriptor.value;
return descriptor;
};
}