nestjs-otel icon indicating copy to clipboard operation
nestjs-otel copied to clipboard

Access function args in @Span decorator

Open dan-cooke opened this issue 1 year ago • 1 comments

First of all ,thanks for the great library! Its been a joy to work with so far

One pattern that I have noticed myself duplicating almost everywhere is this

  @Span('DocumentsService.create')
  async create(userId: string, orgId: string, dto: CreateDocumentDto) {
    this.traceService.getSpan().setAttributes({
      orgId,
      userId,
      dto: JSON.stringify(dto),
    });
// --- snip

Where I annotate a function with Span, and then immediately set the function args into the span attributes.

Question

Is it possible with reflect-metadata to roll that code into @Span , maybe something like this

@Span('DocumentsService.create', ['userId', 'orgId', 'dto'])

Where users have to explicitly set the fields they want to attach to attributes

I am used to this in rust, but I'm not sure if the TS compiler is capable of something like that

dan-cooke avatar Mar 12 '24 15:03 dan-cooke

Hey! this is a reasonable ask, PRs are welcome but it will be quite hard if you want to customize the attribute names. You might need to think about a more customizable interface

pragmaticivan avatar May 08 '24 03:05 pragmaticivan

This issue was marked stale. It will be closed in 30 days without additional activity.

github-actions[bot] avatar May 08 '25 03:05 github-actions[bot]

Typescript/Javascript cannot reflect function parameter names, so we can only really use the order of the params


I am proposing this API:

@Span(
  'DocumentsService.create',
  (_, userId, orgId, dto) => ({ attributes: { userId, orgId, dto: JSON.stringify(dto) } })
)
async create(ignored: string, userId: string, orgId: string, dto: CreateDocumentDto) {
   // business logic
}

It extends the original one by allowing the second parameter to be SpanOptions, or (...args) => SpanOptions


This allows us to implement any extension in the userland:

const SpanWithAttrs(name: string, attrs: Record<string, number>) =>
  Span(name, (...args) => ({
    attributes: Object.fromEntries(
      Object.entries(attrs).map(([key, index]) => [key, args[index]])
    )
  }))


@SpanWithAttrs('DocumentsService.create', { userId: 1, orgId: 2, dto: 3 })
async create(ignored: string, userId: string, orgId: string, dto: CreateDocumentDto) {
   // business logic
}

Or possibly:


const SpanWithAttrs(name: string, attrs: Array<string | null> =>
  Span(name, (...args) => ({
    attributes: Object.fromEntries(
      attrs.map(key, index) => key && [key, args[index]]).filter(Boolean)
    )
  }))

@SpanWithAttrs('DocumentsService.create', [null,  'userId', 'orgId', 'dto' })
async create(ignored: string, userId: string, orgId: string, dto: CreateDocumentDto) {
   // business logic
}

Which is pretty close to the original proposal.

Papooch avatar May 14 '25 05:05 Papooch