🎭 ハンドラーユーティリティ
ハンドラーラッパーは、任意のハンドラー関数に適用できる横断的関心事を提供します。これらのユーティリティは、ハンドラーのコアビジネスロジックを変更することなく、ログ、ページネーション、その他のミドルウェアライクな機能を追加します。
🎯 概要
ハンドラーラッパーは、同じインターフェースを維持しながらハンドラーに追加機能を強化するユーティリティです。デコレーターパターンに従い、複数のラッパーを組み合わせて複数層の機能を追加できます。
主要機能
- 非侵入的: ハンドラーシグネチャを変更せずに機能を追加
- 構成可能: 同じハンドラーに複数のラッパーを結合
- 設定可能: 各ラッパーの柔軟な設定オプション
- 型安全: 適切な型付けによる完全なTypeScriptサポート
📝 関数ログ
withLogging
柔軟なパラメータ順序で包括的なログ機能を提供するため、任意の関数をラップします。
import { primitives } from '@nodeblocks/backend-sdk';
const { withLogging } = primitives;
// デフォルト設定でのシンプルログ
const loggedHandler = withLogging(createUserHandler);
// ログレベルを指定
const debugHandler = withLogging(createUserHandler, 'debug');
// カスタムログを指定
const customLoggedHandler = withLogging(createUserHandler, customLogger);
// ログとレベルの両方を指定(任意の順序)
const fullLoggedHandler = withLogging(createUserHandler, customLogger, 'trace');
ログレベル
// 利用可能なログレベル
withLogging(handler, 'trace'); // 最も詳細
withLogging(handler, 'debug'); // 開発情報
withLogging(handler, 'info'); // 一般情報
withLogging(handler, 'warn'); // 警告
withLogging(handler, 'error'); // エラー
withLogging(handler, 'fatal'); // 重大なエラー
ログ出力
すべてのログレベルで以下を含む構造化ログを生成:
- 関数名
- サニタイズされた入力引数
- サニタイズされた結果/戻り値
- 非同期関数のPromise検出
{
"args": [{"params": {"requestBody": {"name": "John"}}}],
"functionName": "createUserHandler",
"msg": "Function: createUserHandler",
"result": {"user": {"id": "123", "name": "John"}}
}
スマートサニタイゼーション
// 機密データは自動的にサニタイズ
const sensitiveData = {
username: 'john',
password: 'secret123', // '[SANITIZED]'に置換
email: 'john@example.com',
token: 'jwt-token', // '[SANITIZED]'に置換
apiKey: 'api-key-123' // '[SANITIZED]'に置換
};
const loggedHandler = withLogging(userHandler, 'info');
// パスワード、トークン、APIキーは自動的に非表示
カスタムログの使用
import { createLogger } from 'pino';
// カスタムPinoログを作成
const customLogger = createLogger({
level: 'debug',
prettyPrint: {
colorize: true,
translateTime: 'SYS:yyyy-mm-dd HH:MM:ss'
}
});
// カスタムログでハンドラーをラップ
const loggedHandler = withLogging(createUserHandler, customLogger, 'debug');
📄 ページネーション
withPagination
データベース操作に自動ページネーションサポートを追加します。
import { primitives } from '@nodeblocks/backend-sdk';
const { withPagination } = primitives;
// ページネーション付きハンドラー
const paginatedUsers = withPagination(findUsersHandler, {
defaultLimit: 20, // デフォルトページサイズ
maxLimit: 100, // 最大ページサイズ
defaultPage: 1 // デフォルトページ番号
});
ページネーション設定
interface PaginationOptions {
defaultLimit?: number; // デフォルト: 20
maxLimit?: number; // デフォルト: 100
defaultPage?: number; // デフォルト: 1
limitParam?: string; // デフォルト: 'limit'
pageParam?: string; // デフォルト: 'page'
}
const customPagination = withPagination(handler, {
defaultLimit: 10,
maxLimit: 50,
limitParam: 'size',
pageParam: 'offset'
});
ページネーション応答
// 自動的に追加されるページネーションメタデータ
{
"data": [...], // 実際のデータ
"pagination": {
"page": 1,
"limit": 20,
"total": 150,
"totalPages": 8,
"hasNext": true,
"hasPrev": false
}
}
🔄 ラッパーコンポジション
複数ラッパーの組み合わせ
import { primitives } from '@nodeblocks/backend-sdk';
const { withLogging, withPagination, compose } = primitives;
// 複数ラッパーを適用
const enhancedHandler = compose(
withLogging(
withPagination(findUsersHandler, { defaultLimit: 10 }),
'debug'
)
);
// 別の組み合わせ方法
const loggedPaginatedHandler = withLogging(
withPagination(findUsersHandler),
'info'
);
カスタムラッパーの作成
// カスタムタイミングラッパー
const withTiming = <T extends (...args: any[]) => any>(
handler: T,
label?: string
): T => {
return ((...args: any[]) => {
const start = Date.now();
const result = handler(...args);
if (result && typeof result.then === 'function') {
// 非同期関数の場合
return result.finally(() => {
const duration = Date.now() - start;
console.log(`${label || handler.name} took ${duration}ms`);
});
} else {
// 同期関数の場合
const duration = Date.now() - start;
console.log(`${label || handler.name} took ${duration}ms`);
return result;
}
}) as T;
};
// 使用例
const timedHandler = withTiming(createUserHandler, 'User Creation');
エラーハンドリングラッパー
// カスタムエラーラッパー
const withErrorHandling = <T extends (...args: any[]) => any>(
handler: T,
errorHandler?: (error: any) => void
): T => {
return ((...args: any[]) => {
try {
const result = handler(...args);
if (result && typeof result.catch === 'function') {
return result.catch((error: any) => {
if (errorHandler) {
errorHandler(error);
}
throw error;
});
}
return result;
} catch (error) {
if (errorHandler) {
errorHandler(error);
}
throw error;
}
}) as T;
};
// カスタムエラーハンドラー付きで使用
const safeHandler = withErrorHandling(
createUserHandler,
(error) => {
console.error('Handler error:', error.message);
// エラーメトリクスの送信など
}
);
🎯 実用的な使用例
完全に強化されたハンドラー
import { primitives, utils } from '@nodeblocks/backend-sdk';
const { withLogging, withPagination } = primitives;
const { nodeblocksLogger } = utils;
// 基本ハンドラー
const findUsersHandler = async (payload: RouteHandlerPayload) => {
const { context } = payload;
const users = await context.db.users.find({}).toArray();
return ok(mergeData(payload, { users }));
};
// 完全に強化されたハンドラー
const enhancedUsersHandler = withLogging(
withPagination(findUsersHandler, {
defaultLimit: 25,
maxLimit: 100
}),
nodeblocksLogger,
'info'
);
// ルートで使用
export const findUsersRoute = withRoute({
method: 'GET',
path: '/users',
handler: compose(
enhancedUsersHandler,
lift(listTerminator)
)
});
開発対本番のラッパー
const isDevelopment = process.env.NODE_ENV === 'development';
// 開発環境でのみ詳細ログ
const environmentHandler = isDevelopment
? withLogging(createUserHandler, 'debug')
: createUserHandler;
// 条件付きページネーション
const paginatedHandler = process.env.ENABLE_PAGINATION === 'true'
? withPagination(findItemsHandler, { defaultLimit: 50 })
: findItemsHandler;
🎯 ベストプラクティス
1. 適切なログレベル
// 良い - 環境に応じたログレベル
const logLevel = process.env.NODE_ENV === 'production' ? 'warn' : 'debug';
const loggedHandler = withLogging(handler, logLevel);
// 悪い - 本番環境でのデバッグログ
const loggedHandler = withLogging(handler, 'debug'); // 本番環境で過度
2. ページネーション制限
// 良い - 適切な制限
const paginatedHandler = withPagination(handler, {
defaultLimit: 20,
maxLimit: 100 // DoS攻撃を防ぐ
});
// 悪い - 無制限ページネーション
const paginatedHandler = withPagination(handler, {
maxLimit: Infinity // 危険
});
3. ラッパー順序
// 良い - 論理的な順序(内側から外側へ)
const handler = withLogging(
withPagination(
withErrorHandling(baseHandler)
)
);
// 悪い - 非論理的な順序
const handler = withPagination(
withLogging(
withErrorHandling(baseHandler)
)
); // ページネーションがログされない
➡️ 次へ
ログユーティリティについて学習して、構造化ログとHTTPリクエストログの設定方法を理解しましょう!