HarmonyOS7 AOP 能干嘛?无侵入性能监控和日志埋点实战

发布时间:2026/7/1 18:24:39

HarmonyOS7 AOP 能干嘛?无侵入性能监控和日志埋点实战 文章目录前言ArkTS 里的 AOP装饰器就是切面第一个切面LogExecutionTrackTime性能耗时追踪CatchError统一异常捕获全链路追踪组合装饰器PerfMonitor 数据收集踩过的坑小结前言你有没有过这种体验业务代码写得好好的PM 说加个性能监控于是你在每个方法前后都塞上了Date.now()和console.info。代码瞬间变得又长又丑业务逻辑和监控代码搅在一起改起来特别痛苦。这就是典型的横切关注点问题——日志、性能监控、错误捕获这些逻辑跟业务本身没关系但又散落在到处都是。AOP面向切面编程就是专门搞定这类事的。ArkTS 里的 AOP装饰器就是切面在 Java 世界AOP 靠的是 Spring AOP 或者 AspectJ底层用动态代理或字节码织入。ArkTS 没这么复杂——装饰器天然就是 AOP 的实现方式。方法装饰器可以在方法执行前后插入逻辑完全不需要修改原始代码。这比 Java 那套优雅多了。第一个切面LogExecution先来个最简单的自动记录方法调用和参数// decorators/LogExecution.etsfunctionLogExecution(target:Object,propertyKey:string,descriptor:PropertyDescriptor):PropertyDescriptor{constoriginalMethoddescriptor.value descriptor.valuefunction(...args:any[]){conststartDate.now()console.info([Log] 调用${propertyKey}参数:${JSON.stringify(args)})try{constresultoriginalMethod.apply(this,args)constelapsedDate.now()-startconsole.info([Log]${propertyKey}完成耗时${elapsed}ms)returnresult}catch(e){console.error([Log]${propertyKey}异常:${e})throwe}}returndescriptor}用法特别简单往方法上一贴就行classUserService{LogExecutionasyncfetchUser(userId:string):PromiseUser{constresponseawaithttp.request(/api/users/${userId})returnJSON.parse(response.resultasstring)}}业务代码干干净净日志逻辑全在装饰器里。想加就加想摘就摘零侵入。TrackTime性能耗时追踪做性能优化的时候需要知道每个关键方法的实际耗时。手动埋点太累用装饰器批量搞定// decorators/TrackTime.etsimport{PerfMonitor}from../monitor/PerfMonitorfunctionTrackTime(tag?:string){returnfunction(target:Object,propertyKey:string,descriptor:PropertyDescriptor):PropertyDescriptor{constoriginalMethoddescriptor.valueconstlabeltag??propertyKey descriptor.valuefunction(...args:any[]){conststartDate.now()PerfMonitor.getInstance().markStart(label)constfinalize(){constelapsedDate.now()-start PerfMonitor.getInstance().record(label,elapsed)if(elapsed100){console.warn([Perf] 慢方法告警:${label}耗时${elapsed}ms)}}try{constresultoriginalMethod.apply(this,args)// 处理异步方法if(resultinstanceofPromise){returnresult.then((val){finalize();returnval},(err){finalize();throwerr})}finalize()returnresult}catch(e){finalize()throwe}}returndescriptor}}这里有个细节——异步方法需要特殊处理。如果直接finalize()拿到的是同步耗时没有意义。判断返回值是不是 Promise是的话就在.then()里再记录。CatchError统一异常捕获每个方法都写 try-catch 太烦了。装饰器统一管理// decorators/CatchError.etsfunctionCatchError(fallbackValue?:any){returnfunction(target:Object,propertyKey:string,descriptor:PropertyDescriptor):PropertyDescriptor{constoriginalMethoddescriptor.value descriptor.valueasyncfunction(...args:any[]){try{returnawaitoriginalMethod.apply(this,args)}catch(e){// 上报到错误监控平台ErrorReporter.getInstance().report({method:propertyKey,error:e,args,timestamp:Date.now()})// 返回降级值避免页面崩溃if(fallbackValue!undefined){console.warn([CatchError]${propertyKey}异常返回降级值)returnfallbackValue}throwe}}returndescriptor}}有了这个装饰器网络请求失败、数据解析出错这些场景都能优雅降级不会直接白屏classProductRepository{CatchError([])asyncgetProductList():PromiseProduct[]{constdataawaithttp.request(/api/products)returnJSON.parse(data.resultasstring)}CatchError(null)asyncgetProductDetail(id:string):PromiseProduct|null{constdataawaithttp.request(/api/products/${id})returnJSON.parse(data.resultasstring)}}全链路追踪组合装饰器单个装饰器好用但实际项目中经常需要多个切面叠加。来个全链路追踪的完整实现// monitor/TraceContext.etsexportclassTraceContext{privatestaticinstance:TraceContextprivatetraces:Mapstring,TraceSpan[]newMap()privatecurrentTraceId:stringstaticgetInstance():TraceContext{if(!TraceContext.instance){TraceContext.instancenewTraceContext()}returnTraceContext.instance}beginTrace(traceId:string):void{this.currentTraceIdtraceIdthis.traces.set(traceId,[])}addSpan(span:TraceSpan):void{constspansthis.traces.get(this.currentTraceId)if(spans){spans.push(span)}}endTrace():TraceReport{constspansthis.traces.get(this.currentTraceId)??[]consttotalspans.reduce((sum,s)sums.duration,0)return{traceId:this.currentTraceId,spans,totalDuration:total,spanCount:spans.length}}}exportinterfaceTraceSpan{name:stringstartTime:numberduration:numberstatus:success|error}exportinterfaceTraceReport{traceId:stringspans:TraceSpan[]totalDuration:numberspanCount:number}然后是串联多个装饰器的组合装饰器// decorators/Traced.etsfunctionTraced(spanName?:string){returnfunction(target:Object,propertyKey:string,descriptor:PropertyDescriptor):PropertyDescriptor{constoriginalMethoddescriptor.valueconstnamespanName??propertyKey descriptor.valueasyncfunction(...args:any[]){conststartDate.now()letstatus:success|errorsuccesstry{constresultawaitoriginalMethod.apply(this,args)returnresult}catch(e){statuserrorErrorReporter.getInstance().report({method:name,error:e,args})throwe}finally{TraceContext.getInstance().addSpan({name,startTime:start,duration:Date.now()-start,status})}}returndescriptor}}现在给一个完整的用户下单流程打上追踪classOrderService{Traced(创建订单)asynccreateOrder(items:CartItem[]):PromiseOrder{constorderawaitthis.buildOrder(items)awaitthis.validateStock(order)awaitthis.submitOrder(order)returnorder}Traced(构建订单)privateasyncbuildOrder(items:CartItem[]):PromiseOrder{// ...}Traced(校验库存)CatchError(null)privateasyncvalidateStock(order:Order):Promiseboolean{// ...}Traced(提交订单)privateasyncsubmitOrder(order:Order):Promisevoid{// ...}}调用createOrder后就能拿到整条链路上每个步骤的耗时和状态。哪个环节慢了、哪个环节报错了一目了然。PerfMonitor 数据收集前面用到的PerfMonitor简单实现一下// monitor/PerfMonitor.etsexportclassPerfMonitor{privatestaticinstance:PerfMonitorprivaterecords:Mapstring,number[]newMap()staticgetInstance():PerfMonitor{if(!PerfMonitor.instance){PerfMonitor.instancenewPerfMonitor()}returnPerfMonitor.instance}markStart(label:string):void{// 可扩展记录开始时间戳}record(label:string,duration:number):void{if(!this.records.has(label)){this.records.set(label,[])}this.records.get(label)!.push(duration)}getStats(label:string):{avg:number;max:number;count:number}{constdurationsthis.records.get(label)??[]if(durations.length0)return{avg:0,max:0,count:0}return{avg:durations.reduce((a,b)ab,0)/durations.length,max:Math.max(...durations),count:durations.length}}dumpReport():string{constlines:string[][ 性能报告 ]this.records.forEach((durations,label){constavg(durations.reduce((a,b)ab,0)/durations.length).toFixed(1)constmaxMath.max(...durations)lines.push(${label}: 平均${avg}ms, 最大${max}ms, 调用${durations.length}次)})returnlines.join(\n)}}踩过的坑装饰器顺序很重要。多个装饰器叠加时执行顺序是从下往上靠近方法的先执行。Traced要在CatchError上面这样错误先被捕获追踪记录的 status 才准确。异步方法的耗时统计。很多人忘了async方法的返回值是 Promise直接同步记录拿到的不是真实耗时。一定要判断返回值类型异步的在.then()/.finally()里记录。生产环境记得关掉详细日志。开发阶段全量记录没问题上线后只保留慢方法告警和错误上报不然日志量会很大。小结AOP 在 ArkTS 里用装饰器实现比传统 Java 方案轻量得多。核心思路就是把横切逻辑日志、监控、错误处理从业务代码里抽出来通过装饰器织入。我自己在项目里的做法是给所有网络请求方法和关键业务方法统一加上TracedCatchError一行代码搞定监控和容错。再也不用在业务代码里到处写 try-catch 和Date.now()了。代码干净了监控也没落下。

相关新闻