Files
cherry-studio/packages/mcp-trace/trace-core/core/traceMethod.ts
alickreborn0 3b123863b5 feat: Support LLM Tracing by Alibaba Cloud EDAS product (#7895)
* feat: add tracing modules

* Initial commit

* fix: problem

* fix: update trace web

* fix: trace view

* fix: trace view

* fix: fix some problem

* fix: knowledge and mcp trace

* feat: save trace to user home dir

* feat: open trace with electron browser window

* fix: root trace outputs

* feat: trace internationalization and add trace icon

* feat: add trace title

* feat: update

* package.json添加windows运行script

* feat: update window title

* fix: mcp trace param

* fix: error show

* fix: listTool result

* fix: merge error

* feat: add stream usage and response

* feat: change trace stream

* fix: change stream adapter

* fix: span detail show problem

* fix: process show by time

* fix: stream outputs

* fix: merge problem

* fix: stream outputs

* fix: output text

* fix: EDAS support text

* fix: change trace footer style

* fix: topicId is loaded multiple times

* fix: span reload problem & attribute with cache

* fix: refresh optimization

* Change Powered by text.

* resolve upstream conflicts

* fix: build-time type exception

* fix: exceptions not used when building

* fix: recend no trace

* fix: resend trace list

* fix: delete temporary files

* feat: trace for resend

* fix: trace for resend message with edit

* fix: directory structure and construction method of mcp-trace

* fix: change CRLF to LF

* fix: add function call outputs

* Revert "fix: change CRLF to LF"

* fix: reorganize multi-model display

* fix: append model trace binding topic

* fix: some problems

* fix: code optimization

* fix: delete async

* fix: UI optimization

* fix: sort import

---------

Co-authored-by: 崔顺发 <csf01409784@alibaba-inc.com>
Co-authored-by: 管鑫荣 <gxr01409783@alibaba-inc.com>
2025-07-20 14:53:35 +08:00

164 lines
5.3 KiB
TypeScript

import 'reflect-metadata'
import { SpanStatusCode, trace } from '@opentelemetry/api'
import { context as traceContext } from '@opentelemetry/api'
import { defaultConfig } from '../types/config'
export interface SpanDecoratorOptions {
spanName?: string
traceName?: string
tag?: string
}
export function TraceMethod(traced: SpanDecoratorOptions) {
return function (target: any, propertyKey?: any, descriptor?: PropertyDescriptor | undefined) {
// 兼容静态方法装饰器只传2个参数的情况
if (!descriptor) {
descriptor = Object.getOwnPropertyDescriptor(target, propertyKey)
}
if (!descriptor || typeof descriptor.value !== 'function') {
throw new Error('TraceMethod can only be applied to methods.')
}
const originalMethod = descriptor.value
const traceName = traced.traceName || defaultConfig.defaultTracerName || 'default'
const tracer = trace.getTracer(traceName)
descriptor.value = function (...args: any[]) {
const name = traced.spanName || propertyKey
return tracer.startActiveSpan(name, async (span) => {
try {
span.setAttribute('inputs', convertToString(args))
span.setAttribute('tags', traced.tag || '')
const result = await originalMethod.apply(this, args)
span.setAttribute('outputs', convertToString(result))
span.setStatus({ code: SpanStatusCode.OK })
return result
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error))
span.setStatus({
code: SpanStatusCode.ERROR,
message: err.message
})
span.recordException(err)
throw error
} finally {
span.end()
}
})
}
return descriptor
}
}
export function TraceProperty(traced: SpanDecoratorOptions) {
return (target: any, propertyKey: string, descriptor?: PropertyDescriptor) => {
// 处理箭头函数类属性
const traceName = traced.traceName || defaultConfig.defaultTracerName || 'default'
const tracer = trace.getTracer(traceName)
const name = traced.spanName || propertyKey
if (!descriptor) {
const originalValue = target[propertyKey]
Object.defineProperty(target, propertyKey, {
value: async function (...args: any[]) {
const span = tracer.startSpan(name)
try {
span.setAttribute('inputs', convertToString(args))
span.setAttribute('tags', traced.tag || '')
const result = await originalValue.apply(this, args)
span.setAttribute('outputs', convertToString(result))
return result
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error))
span.recordException(err)
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message })
throw error
} finally {
span.end()
}
},
configurable: true,
writable: true
})
return
}
// 标准方法装饰器逻辑
const originalMethod = descriptor.value
descriptor.value = async function (...args: any[]) {
const span = tracer.startSpan(name)
try {
span.setAttribute('inputs', convertToString(args))
span.setAttribute('tags', traced.tag || '')
const result = await originalMethod.apply(this, args)
span.setAttribute('outputs', convertToString(result))
return result
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error))
span.recordException(err)
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message })
throw error
} finally {
span.end()
}
}
}
}
export function withSpanFunc<F extends (...args: any[]) => any>(
name: string,
tag: string,
fn: F,
args: Parameters<F>
): ReturnType<F> {
const traceName = defaultConfig.defaultTracerName || 'default'
const tracer = trace.getTracer(traceName)
const _name = name || fn.name || 'anonymousFunction'
return traceContext.with(traceContext.active(), () =>
tracer.startActiveSpan(
_name,
{
attributes: {
tags: tag || '',
inputs: JSON.stringify(args)
}
},
(span) => {
// 在这里调用原始函数
const result = fn(...args)
if (result instanceof Promise) {
return result
.then((res) => {
span.setStatus({ code: SpanStatusCode.OK })
span.setAttribute('outputs', convertToString(res))
return res
})
.catch((error) => {
const err = error instanceof Error ? error : new Error(String(error))
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message })
span.recordException(err)
throw error
})
.finally(() => span.end())
} else {
span.setStatus({ code: SpanStatusCode.OK })
span.setAttribute('outputs', convertToString(result))
span.end()
}
return result
}
)
)
}
function convertToString(args: any | any[]): string | boolean | number {
if (typeof args === 'string' || typeof args === 'boolean' || typeof args === 'number') {
return args
}
return JSON.stringify(args)
}