fuels-ts 脚本日志解析实战用logs与groupedLogs读取 Sway 脚本日志【免费下载链接】fuels-tsFuel Network Typescript SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-ts本文以 Fuel Network TypeScript SDKfuels-ts的官方指南 apps/docs/src/guide/scripts/logs.md 为核心系统讲解如何在 TypeScript 侧解析 Sway 脚本Script在执行过程中产生的日志。脚本一旦在 Sway 中调用log(...)就会产生一条写入交易回执receipt的日志条目同时变量类型会记录在脚本的 ABI 中本指南将演示 SDK 如何借助 ABI 将这些原始数据解析为 TypeScript 类型并通过脚本调用响应的logs与groupedLogs属性按需读取。读完本文你将掌握「读取脚本自身日志」与「按合约/脚本来源区分读取分组日志」两种模式并理解其背后的回执解码链路。背景Sway 的log与 SDK 的日志解码机制在 Fuel 链上无论是 Sway 合约还是脚本都可以通过内置的log()函数打印值。从 fuels-ts 的角度看日志的流转过程是脚本执行到log(value)时Fuel 虚拟机把日志写入交易结果TransactionResult的回执数组receipts中日志条目的具体字段类型由程序 ABI 记录fuels-ts 拿到回执后结合本次调用涉及的 ABI脚本自身 ABI 被调用合约的 ABI把原始回执解码成真实的 TypeScript 值。因此日志解析的前提条件是代码已通过 typegen 生成类型安全的脚本/合约封装本文示例中的ScriptLogSimple、ScriptLogWithContract即生成产物。关于脚本实例化与调用的一般流程可参考 运行脚本指南。解码结果统一放在脚本调用响应的两个属性中logs一维数组按发生顺序汇总本次调用产生的全部日志groupedLogs对象Recordstring, T[]按日志来源合约地址 / 脚本占位地址分组。下面分别通过两个官方示例说明。简单场景用logs读取脚本自身日志Sway 侧一段只打日志的脚本官方示例脚本位于 apps/docs/sway/script-log-simple/src/main.swSway 代码非常简短script; fn main(log_value: str[7]) - str[7] { log(log_value); log_value }要点分析函数入参log_value: str[7]是一个长度固定的字符串数组string array类型str[7]表示恰好容纳 7 个字符log(log_value)是 Sway 内置日志函数把该字符串写入回执函数随后将入参原样返回这并不影响日志行为只是展示「脚本既能返回值、也能附带日志」的常见形态。TypeScript 侧读取logs属性对应 TypeScript 示例见 apps/docs/src/guide/scripts/snippets/script-log-simple.ts完整代码如下import { Provider, Wallet } from fuels; import { WALLET_PVT_KEY, LOCAL_NETWORK_URL } from ../../../env; import { ScriptLogSimple } from ../../../typegend; const provider new Provider(LOCAL_NETWORK_URL); const wallet Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); const script new ScriptLogSimple(wallet); const { waitForResult } await script.functions.main(ScriptA).call(); const { logs } await waitForResult(); // logs: [ScriptA]逐步拆解这段代码连接与签名者准备new Provider(LOCAL_NETWORK_URL)建立与本地 Fuel 节点的连接Wallet.fromPrivateKey(WALLET_PVT_KEY, provider)用测试私钥派生签名者。LOCAL_NETWORK_URL与WALLET_PVT_KEY是文档项目测试环境变量见 apps/docs/src/env.ts 及docs应用的package.json脚本通常由本地启动的 Fuel 节点提供。实例化脚本封装new ScriptLogSimple(wallet)使用 typegen 生成的类创建脚本调用载体其方法名与 ABI 中的main一一对应。发起调用script.functions.main(ScriptA).call()把字符串参数编码进脚本字节码输入并提交返回调用句柄。注意ScriptA恰好 7 个字符与 Sway 侧str[7]类型匹配。等待出块并取日志await waitForResult()等待交易被确认也可用 pre-confirmation 流程提前拿到结果解构出logs属性。执行后logs的值就是[ScriptA]—— Sway 里log(log_value)记录的原始值在 SDK 侧已被还原为同等的 TypeScript 字符串。示例文件末尾还附带了断言逻辑校验日志长度与内容用于测试场景const expectedLogs [ScriptA]; console.log( Logs should have the same length, logs.length expectedLogs.length ); console.log( Logs should be in the correct format, expectedLogs.every((log) logs.includes(log)) );分组场景用groupedLogs区分日志来源当脚本调用合约、而合约内部也打印日志时仅凭一维的logs很难判断每条日志出自谁。此时应使用响应的groupedLogs属性——它按「日志产生者的地址」把日志归类。Sway 侧脚本内跨合约调用并各自打日志示例脚本位于 apps/docs/sway/script-log-with-contract/src/main.swscript; use log_simple_abi::LogSimple; fn main(contract_id: b256) { log(Script started); let log_contract abi(LogSimple, contract_id); log_contract.log_simple(__to_str_array(ContractA)); log(Script finished); }要点分析脚本先log(Script started)随后通过 ABI 接口LogSimple调用外部合约入参contract_id: b256是合约地址让合约打印ContractA脚本最后再log(Script finished)被调合约的 ABI 定义在 apps/docs/sway/log-simple-abi/src/main.swabi LogSimple { fn log_simple(val: str[9]); }即接收一个 9 字符的定长字符串并打印__to_str_array(ContractA)是 Sway 提供的字符串字面量转定长数组辅助函数配合str[9]使用。TypeScript 侧按来源读取groupedLogs完整示例见 apps/docs/src/guide/scripts/snippets/script-log-with-contract.tsimport { Provider, Wallet, ZeroBytes32 } from fuels; import { WALLET_PVT_KEY, LOCAL_NETWORK_URL } from ../../../env; import { LogSimpleFactory, ScriptLogWithContract } from ../../../typegend; const provider new Provider(LOCAL_NETWORK_URL); const wallet Wallet.fromPrivateKey(WALLET_PVT_KEY, provider); // Create a contract instance const { waitForResult: waitForDeploy } await LogSimpleFactory.deploy(wallet); const { contract } await waitForDeploy(); // Create a script instance const script new ScriptLogWithContract(wallet); // Call the script const { waitForResult } await script.functions .main(contract.id.toB256()) .addContracts([contract]) .call(); // Wait for the script to finish and get the logs const { groupedLogs } await waitForResult(); // groupedLogs { // [ZeroBytes32]: [Script started, Script finished], // [contract.id.toB256()]: [ContractA], // }与简单场景相比这里有三个关键差异先部署被调合约LogSimpleFactory.deploy(wallet)是 typegen 生成的合约工厂waitForDeploy()返回{ contract }拿到合约实例及其id。把合约加入调用上下文script.functions.main(contract.id.toB256()).addContracts([contract]).call()中contract.id.toB256()作为 Sway 侧main的参数b256地址.addContracts([contract])则告诉 SDK 本次调用会触及该合约从而把它的 ABI 纳入日志解码范围。这一步对分组日志的解码至关重要——缺少 ABIContractA这类值将无法还原类型。读取groupedLogs结果是以 32 字节十六进制地址为键的对象。脚本没有地址为何使用ZeroBytes32需要特别说明的是脚本Script本身没有 ID 或地址但它仍能产生日志。为了让分组结果统一、可索引SDK 使用一个全零的十六进制地址32 字节 b256 常量即ZeroBytes32值为0x0000...00作为「脚本自身」的分组键。因此上面示例的结果结构为groupedLogs[ZeroBytes32]→ 脚本直接打印的两条[Script started, Script finished]groupedLogs[contract.id.toB256()]→ 被调合约打印的一条[ContractA]。示例文件尾部同样提供了针对该结构的断言console.log( Grouped logs should be defined, typeof groupedLogs ! undefined ); console.log( Should have zeroed log, typeof groupedLogs[ZeroBytes32] ! undefined ); console.log( Should script logs [Script Started], groupedLogs[ZeroBytes32].includes(Script started) ); console.log( Should script logs [Script Finished], groupedLogs[ZeroBytes32].includes(Script finished) );在实际应用中你可以通过判断键是否等于ZeroBytes32来区分「脚本自身日志」与「合约日志」从而在 UI、事件总线或测试断言中按来源分派处理。深入底层logs/groupedLogs是如何解码出来的了解了用法后不妨顺着源码确认一下 SDK 内部的处理链路本仓库内实现路径如下。当脚本调用等待结果时FunctionResult的组装集中在 packages/program/src/response.ts 的buildFunctionResult。其流程是先transactionResponse.waitForResult()拿到交易结果与回执数组再通过getAllResultLogs({ receipts, mainCallConfig, functionScopes })同时产出logs与groupedLogs见该文件第 136–168 行最终把两者随value、gasUsed、transactionId等一起放入FunctionResult返回。也就是说logs/groupedLogs并非交易响应的原始字段而是 SDK 在每次调用后即时解码生成的。更底层的逐条解码与分组逻辑位于 packages/account/src/providers/transaction-response/getAllDecodedLogs.ts 的getAllDecodedLogs它遍历每条回执结合本次调用收集到的全部 ABI脚本 ABI .addContracts()传入的合约 ABI把 Log 数据解码为类型化条目logEntry解码结果以归约reduce方式累积累积器始终维护{ logs, groupedLogs }logs是平铺的完整列表groupedLogs[receipt.id]则把每条日志追加到其来源地址即回执中的id对应的分组数组中。从源码结构可以推断正因为分组键直接取自回执的id字段而被调合约执行日志时其id即为合约地址脚本自身的日志则需要一个占位标识——这就是官方文档强调使用零地址ZeroBytes32的原因。另外仓库的集成测试也对分组日志有直接覆盖例如 packages/fuel-gauge/src/advanced-logging.test.ts 等多处用例都解构并断言了groupedLogs可作为理解实际数据结构与断言的参考。小结与使用建议只关心脚本自身打印的值用脚本调用响应的logs一维数组按顺序读取即可前提是传入的参数类型与 Sway 侧的定长类型如str[7]严格一致。脚本调用了合约、需要区分日志来源用groupedLogs并以ZeroBytes32定位脚本日志、以contract.id.toB256()定位各合约日志调用时务必通过.addContracts([...])把涉及合约的 ABI 提供给 SDK否则对应日志无法被正确解码。注意解码前置条件日志值能还原为 TypeScript 类型依赖 typegen 生成的封装类以及本地运行节点LOCAL_NETWORK_URL与测试钱包WALLET_PVT_KEY就绪相关运行环境的搭建可参考 部署脚本指南 与 运行脚本指南。【免费下载链接】fuels-tsFuel Network Typescript SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-ts创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考