elastic / apm-agent-nodejs

https://www.elastic.co/guide/en/apm/agent/nodejs/current/index.html
BSD 2-Clause "Simplified" License
582 stars 224 forks source link

Mysql span not working #2090

Open exejutable opened 3 years ago

exejutable commented 3 years ago

Describe the bug

When i make a http request the apm stats dont have the mysql span, i have apm in the top of my app

App entrypoint

import './lib/elk/apm';
import api from './api';
import { CONFIGURATION } from './lib/config/config';
import { banner } from './lib/banner';

api.listen(CONFIGURATION.app.port, () => {
  banner();
});

Apm file ./lib/elk/apm

import agent from 'elastic-apm-node';
import { CONFIGURATION } from '../config/config';

export const apm = agent.start({

  secretToken: CONFIGURATION.elk.apm.secretToken,

  serverUrl: CONFIGURATION.elk.apm.serverUrl,

  environment: CONFIGURATION.environment,

  captureBody: 'all',

  logLevel: 'debug',

});

export default apm;

To Reproduce

I only do a http request to the my service with a sql select

Expected behavior

See the span in apm

Environment (please complete the following information)

How are you starting the agent? (please tick one of the boxes)

Additional context

Sidenotes

I have another app running with the same config and everithing works fine

Additional context for the second app

astorm commented 3 years ago

@exejutable Thanks for reporting! Two quick questions

  1. What are you using to enable ES6 imports in Node.js? Is the agent still the first thing in your bundled/compiled source?
  2. Do you have an example of the code that makes the MySQL query? The important bits will be how you're importing/requiring the mysql library, and which method/methods you're using to make your query.
exejutable commented 3 years ago

Im using ts-node, al the code is in typescript

  1. Im using ts-node, all the code is in typescript, the agent is the first import in the application
  2. Yes sure

tsconfig.json


{
  "ts-node": {
    "transpileOnly": true
  },
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "pretty": true,
    "sourceMap": true,
    "outDir": "dist",
    "importHelpers": true,
    "strict": true,
    "noImplicitAny": false,
    "strictNullChecks": false,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "moduleResolution": "node",
    "baseUrl": ".",
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": [
      "es5",
      "es6",
      "dom",
      "es2015.core",
      "es2015.collection",
      "es2015.generator",
      "es2015.iterable",
      "es2015.promise",
      "es2015.proxy",
      "es2015.reflect",
      "es2015.symbol",
      "es2015.symbol.wellknown",
      "esnext.asynciterable"
    ]
  }
}

The Base code

import {Pool, PoolConnection} from 'promise-mysql';
import {Service} from 'typedi';
import {MySqlProvider} from "../../lib/database/MySqlProvider";

@Service()
export class CouponRepository  {

  protected pool: Pool;

  constructor(
    private mySqlProvider: MySqlProvider,
  ) {
    this.pool = this.mySqlProvider.pool;
  }

  public async findOne(id: number): Promise<any[]> {
    let poolConnection: PoolConnection = await this.pool.getConnection();

    let query = poolConnection.query({
      sql: `SELECT id, name, code, tenant, description, start_date, end_date, allowed_uses, current_uses, discount, type, created_at, updated_at, deleted_at FROM coupons where id = ?`,
      values: [id]
    });

    poolConnection.release();

    return query;
  }

}

MySqlProvider.ts


import { createPool, Pool, PoolConfig } from 'promise-mysql';
import { Service } from 'typedi';
import { Application } from 'express';
import { Bootstrap, Log, LogService } from '@dg/rest-common';
import { CONFIGURATION } from '../config/config';

@Service()
@Log()
export class MySqlProvider implements Bootstrap {
    private _pool: Pool;

    public name = MySqlProvider.name;

    private logger: LogService;

    public ready: boolean = false;

    private configuration: PoolConfig = {
      host: CONFIGURATION.db.host,
      user: CONFIGURATION.db.username,
      password: CONFIGURATION.db.password,
      port: CONFIGURATION.db.port,
      database: 'coupons',
    };

    isReady(): boolean {
      return this.ready;
    }

    start(app?: Application): void {
      this.logger.info('Connecting mysql client', { meta: { host: this.configuration.host, port: this.configuration.port } });
      this.getPool()
        .then(() => {
          this.ready = true;
          this.logger.info('Mysql connection is ready', { meta: { host: this.configuration.host, port: this.configuration.port } });
        })
        .catch((err) => {
          this.ready = false;
          this.logger.error(`${err.name} : ${err.message}`, { meta: { error: err.stack } });
        });
    }

    public async getPool(): Promise<Pool> {

      if (!this._pool) {
        this._pool = await createPool(this.configuration);
      }

      return this._pool;
    }

    get pool(): Pool {
      return this._pool;
    }

}
astorm commented 3 years ago

@exejutable We're trying to get a reproduction of the behavior you're seeing -- when we do a basic check of the mysql-promise library in an express application router, spans are generated for the query.

app.get('/mysql-test', async (req, res) => {
  const pool = await mysql.createPool({
    connectionLimit : 10,
    host: 'localhost',
    user: 'root',
    password: undefined,
    database: 'test_elastic_apm'
  })

  const poolConnection = await pool.getConnection()

  poolConnection.query({
    sql:sql,
    values:[0]
  }, function (error, results, fields) {
    if (error) {
      console.log(error)
      throw error;
    }
    console.log('query returned ' + results.length)
  });

  poolConnection.release()
})

So this confirms that our base instrumentation is working, but that there's something different about your setup/configuration/usage that's causing these spans to either not be generated, or to be generated outside a transaction, or perhaps the agent itself isn't loading. In order to solve this problem we'll need to be able to reproduce the behavior you're seeing. To that end we have a few more questions.

First -- you said

Im using ts-node, all the code is in typescript, the agent is the first import in the application

Can you confirm that the agent is also still the first require in your compiled source (not just that it's the first import).

Second -- in the case where the you're not seeing MySQL spans, are you seeing spans-or-transactions for the HTTP request? Or are these spans missing as well.

Third -- you you have an example of how the CouponRepository is used from the express route/middleware function? Or is this used outside of a route function?

If you can provide us with answers for as many of the above questions as possible, it may help use narrow in a reproduction of the issue. Once we have a reproduction of the behavior, we'll be able to diagnose what's going on that leads to no spans being generated.