Error while using Elastic node APM with database transactions

Description:
I am using elastic-apm-node to instrument a nestjs API it works perfectly, some weeks ago I needed to added database transactions using sequelize and I got the following error

I tested and If I remove database transactions it works correctly and not show this error.

I tried to search on the forums and and tried a lot of ways to solve this problem, but nothing works.

Thank you in advance!!

Here are some screenshots of the error and more context

Error in the UI

startSpan

Transaction snippet code

If I remove the transaction everything works correctly

Hi @David_Souza ,

the API to start manual spans belongs to the transaction class as shown in Transaction API | APM Node.js Agent Reference [4.x] | Elastic. So you need to use such API in the ongoing transaction and the way of getting it is via apm.currentTransaction property.

By the code snippet given and knowing you're using NestJS I guess you're passing the apm instance through the injector by wrapping it with a module or service, right? If so I would ask

  • How you're starting the NestJS app
  • what exactly is the property Apm.context?
  • Could you share the apm wrapper module?

Cheers,
David

Hey! Thank you for the reply!

Following is my wrapper it is just a .ts file that I import in my services to use

import {
    SpanOptions,
    Span,
    ParameterizedMessageObject,
    CaptureErrorOptions,
    CaptureErrorCallback
} from "elastic-apm-node";
import * as elastic_apm from "elastic-apm-node";

class Apm{
    static span_list: Span[] = [];
    static context = elastic_apm;

    static startSpan (
        name: string | null,
        type: string | null,
        subtype: string | null,
        options?: SpanOptions
    ): Span {
        let span = elastic_apm.startSpan.apply(elastic_apm, arguments)
        this.span_list.push(span);
        return span
    }

    static endSpan(outcome: boolean, result: string|null = null): Span|null {
        if(this.span_list.length > 0){
            let span = this.span_list.pop();
            if(!span){
                span =  Apm.context?.currentSpan;
            }

            if(span){
                result && span?.transaction && (span.transaction.result = typeof result === "object" ? JSON.stringify(result) : result);
                span?.setOutcome(outcome ? "success" : "failure");
                span?.end();
                return span
            }
        }
        return null;
    }

    static endAllSpan(outcome: boolean){
        this.span_list.forEach(span => {
            span.setOutcome(outcome ? "success" : "failure");
            span.end();
        });

        this.span_list = [];
    }

    static end (outcome: boolean, result: string|null = null){
        this.endAllSpan(outcome);
        elastic_apm?.setTransactionOutcome(outcome ? "success" : "failure");
        result && elastic_apm?.endTransaction(result);
    }

    static captureError (
      err: Error | string | ParameterizedMessageObject,
      options?: CaptureErrorOptions,
      callback?: CaptureErrorCallback
    ): void{
        console.error(err);
        elastic_apm.captureError(err, options, callback);
    }
}

export default Apm;

The following is my app.ts that starts my app

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe, VersioningType } from '@nestjs/common';
import {
  DocumentBuilder,
  SwaggerDocumentOptions,
  SwaggerModule,
} from '@nestjs/swagger';
import Apm from '@/apm';
import { GlobalExceptionFilter } from '@/shared/filters/global-exception.filter';

async function bootstrap() {
  Apm.context.start({
    serviceName: process.env.APM_SERVICE_NAME,
    secretToken: process.env.APM_TOKEN,
    serverUrl: process.env.APM_URL,
    environment: process.env.APP_ENV,
    opentelemetryBridgeEnabled: true,
  });

  process.env.TZ = 'America/Sao_Paulo';
  const app = await NestFactory.create(AppModule);
  app.enableVersioning({ type: VersioningType.URI, defaultVersion: '1' });
  app.useGlobalFilters(new GlobalExceptionFilter());
  app.useGlobalPipes(
    new ValidationPipe({
      transform: true,
      disableErrorMessages: false,
    }),
  );
  app.enableCors({ origin: '*' });

  const config = new DocumentBuilder()
    .setTitle('Api de Pedidos')
    .setDescription('Api de pedidos - Estadão')
    .setVersion('1')
    .addApiKey(
      { type: 'apiKey', name: 'Authorization', in: 'header' },
      'api-key',
    )

    .addServer(`http://localhost:${process.env.APP_PORT}`, 'Ambiente Local')
    .build();
  const options: SwaggerDocumentOptions = {
    ignoreGlobalPrefix: false,
  };
  const document = SwaggerModule.createDocument(app, config, options);
  SwaggerModule.setup('docs', app, document);
  await app.listen(process.env.APP_PORT);
}

bootstrap();

I don't understand exactly this question * what exactly is the property Apm.context?

Something really strange is that when I remove database transaction it works.

Thank you!!