Swagger compatibility?

Hi,

I used swagger-codegen to generate an express.js application (https://swagger.io/). I have several microservices generated in this fashion, Hence, why apm is so appealing ot me.

When I try to add the apm agent to express microservice (the very first lines in my app.js, nothing is above the code). Here is the code:

// Add this to the VERY top of the first file loaded in your app
var apm = require('elastic-apm-node').start({
    // Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)
    serviceName: 'my_service',

    // Set custom APM Server URL (default: http://localhost:8200)
    serverUrl: 'http://apm:8200'
});

Here are relevant entries from package.json:

"elastic-apm-node": "^1.11.0",
"express": "^4.12.3",
"swagger-express-mw": "^0.1.0",

I'm running apm-server 6.4.

I really want to get apm working, does anyone have any suggestions?

Just to follow up, I just did a run with debug enabled. Here is the output from startup to hitting a simple /v1.0/version route:

my_service-dev_1  | shimming Module._load function
my_service-dev_1  | null
my_service-dev_1  | shimming https@8.11.3 module
my_service-dev_1  | shimming https.Server.prototype.emit function
my_service-dev_1  | shimming mongodb-core@3.1.0 module
my_service-dev_1  | shimming mongodb-core.Server.prototype.command
my_service-dev_1  | shimming mongodb-core.Server.prototype functions: [ 'insert', 'update', 'remove', 'auth' ]
my_service-dev_1  | shimming mongodb-core.Cursor.prototype functions: [ '_find', '_getmore' ]
my_service-dev_1  | shimming bluebird@3.5.0 module
my_service-dev_1  | shimming bluebird.prototype functions: [ '_then', '_addCallbacks' ]
my_service-dev_1  | shimming bluebird.config
my_service-dev_1  | shimming http@8.11.3 module
my_service-dev_1  | shimming http.Server.prototype.emit function
my_service-dev_1  | shimming http.request function
my_service-dev_1  | shimming http.ServerResponse.prototype.writeHead function
my_service-dev_1  | shimming express@4.16.3 module
my_service-dev_1  | shimming express.Router.process_params function
my_service-dev_1  | shimming express.Router.use function
my_service-dev_1  | shimming express.static function
my_service-dev_1  | copying property mime from express.static
my_service-dev_1  | skip shimming layer.handle_request (layer: swaggerUI, path: /)
my_service-dev_1  | skip shimming layer.handle_request (layer: <anonymous>, path: /)
my_service-dev_1  | skip shimming layer.handle_request (layer: <anonymous>, path: /)
my_service-dev_1  | skip shimming layer.handle_request (layer: initialize, path: /)
my_service-dev_1  | skip shimming layer.handle_request (layer: authenticate, path: /)
my_service-dev_1  | (node:18) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
my_service-dev_1  | Could not connect to mongodb on localhost. Ensure that you have mongodb running on localhost and mongodb accepts connections on standard ports!
my_service-dev_1  | intercepted request event call to https.Server.prototype.emit
my_service-dev_1  | setting transaction result { id: '3ec95044-1512-4fc8-8448-ac3d73fcd6f4',
my_service-dev_1  |   result: 'success' }
my_service-dev_1  | start transaction { id: '3ec95044-1512-4fc8-8448-ac3d73fcd6f4',
my_service-dev_1  |   name: undefined,
my_service-dev_1  |   type: undefined }
my_service-dev_1  | shimming express.Router.Layer.handle function
my_service-dev_1  | shimming express.Router.Layer.handle function
my_service-dev_1  | shimming express.Router.Layer.handle function
my_service-dev_1  | shimming express.Router.Layer.handle function
my_service-dev_1  | shimming express.Router.Layer.handle function
my_service-dev_1  | setting transaction result { id: '3ec95044-1512-4fc8-8448-ac3d73fcd6f4',
my_service-dev_1  |   result: 'HTTP 2xx' }
my_service-dev_1  | could not extract route name from request { url: '/v1.0/version',
my_service-dev_1  |   type: 'undefined',
my_service-dev_1  |   null: false,
my_service-dev_1  |   route: false,
my_service-dev_1  |   regex: false,
my_service-dev_1  |   mountstack: false,
my_service-dev_1  |   id: '3ec95044-1512-4fc8-8448-ac3d73fcd6f4' }
my_service-dev_1  | setting default transaction name: GET unknown route { id: '3ec95044-1512-4fc8-8448-ac3d73fcd6f4' }
my_service-dev_1  | adding transaction to queue { id: '3ec95044-1512-4fc8-8448-ac3d73fcd6f4' }
my_service-dev_1  | setting timer to flush queue: 4755ms
my_service-dev_1  | ended transaction { id: '3ec95044-1512-4fc8-8448-ac3d73fcd6f4',
my_service-dev_1  |   type: 'request',
my_service-dev_1  |   result: 'HTTP 2xx',
my_service-dev_1  |   name: 'GET unknown route' }
my_service-dev_1  | intercepted request event call to https.Server.prototype.emit
my_service-dev_1  | setting transaction result { id: '313dd1da-ac68-4e3f-9951-6b0bd2a7ca27',
my_service-dev_1  |   result: 'success' }
my_service-dev_1  | start transaction { id: '313dd1da-ac68-4e3f-9951-6b0bd2a7ca27',
my_service-dev_1  |   name: undefined,
my_service-dev_1  |   type: undefined }
my_service-dev_1  | shimming express.Router.Layer.handle function
my_service-dev_1  | shimming express.Router.Layer.handle function
my_service-dev_1  | setting transaction result { id: '313dd1da-ac68-4e3f-9951-6b0bd2a7ca27',
my_service-dev_1  |   result: 'HTTP 4xx' }
my_service-dev_1  | could not extract route name from request { url: '/favicon.ico',
my_service-dev_1  |   type: 'undefined',
my_service-dev_1  |   null: false,
my_service-dev_1  |   route: false,
my_service-dev_1  |   regex: false,
my_service-dev_1  |   mountstack: false,
my_service-dev_1  |   id: '313dd1da-ac68-4e3f-9951-6b0bd2a7ca27' }
my_service-dev_1  | setting default transaction name: GET unknown route { id: '313dd1da-ac68-4e3f-9951-6b0bd2a7ca27' }
my_service-dev_1  | adding transaction to queue { id: '313dd1da-ac68-4e3f-9951-6b0bd2a7ca27' }
my_service-dev_1  | ended transaction { id: '313dd1da-ac68-4e3f-9951-6b0bd2a7ca27',
my_service-dev_1  |   type: 'request',
my_service-dev_1  |   result: 'HTTP 4xx',
my_service-dev_1  |   name: 'GET unknown route' }
my_service-dev_1  | flushing queue
my_service-dev_1  | sending transactions payload
my_service-dev_1  | no active transaction found - cannot build new span
my_service-dev_1  | intercepted call to http.request { id: null }
my_service-dev_1  | transactions payload successfully sent

Ok, i seem to have resolved this for the time being with apm.setTransactionName(). I set it using req.path.

thanks,
jas

Thanks for trying out Elastic APM and for the very detailed bug report - it's highly appreciated :slight_smile:

I haven't used Swagger my self, but even though it's "just using Express" it sounds to me like it somehow builds the routes in a way that we can't parse.

Can you share a code snippet of how the microservice routes currently are defined?

Cheers,
Thomas

Hi Thomas,

I belive that's abstracted away from me. we define routes in a swagger.yaml file, and then we just write functions in like function my_route(req, res, err) {}. They automtacially get called.

I was able to get away with it by manually setting apm.setTransactionName(req.url) .

Thanks,
jas

Ok thanks for checking. Currently custom Swagger support is not on our roadmap. But I'll make sure to keep an eye out for other users requesting this. If so we might want to consider it.

Your fix by calling apm.setTransactionName(req.url) your self might work fine in your case, but there's some hidden traps that I just want to warn you about:

  • If you have an endpoint that can be called with different HTTP methods (GET, POST, PUT etc). Those will now be joined in the APM UI. I would recommend separating them by calling apm.setTransactionName(req.method + ' ' + req.url) instead
  • If your URLs contain variable data, e.g. a user ID /user/23123 or a blog post title /posts/learn-how-to-program, it will mean that a lot of different transaction groups are being created. This reduces some of the power of Elastic APM and is not recommended. It would be better to name the transactions for instance GET /user/:id or GET /posts/:title. That way a request for two entities of the same type will still be recognized as the same endpoint and grouped together.
1 Like

These are some great tips you've given me. Thank you!

I'll add req.method + req.url like you suggested.

Our api will be using different query parameters and body parameters, which looks like /users?name=x and /users?name=y both show up as /users in APM.

Let me know if that's ok. Thanks again for your help

Ok, great to hear about the query string not being part of the transaction name. That should be fine then :+1:

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.