Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(prometheus): add getMetricsRequestHandler-method to Prometheus #1879

Merged
merged 11 commits into from
Feb 8, 2021
Merged
2 changes: 2 additions & 0 deletions packages/opentelemetry-exporter-prometheus/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@
"devDependencies": {
"@types/mocha": "8.2.0",
"@types/node": "14.14.20",
"@types/sinon": "9.0.10",
"codecov": "3.8.1",
"gts": "2.0.2",
"mocha": "7.2.0",
"nyc": "15.1.0",
"rimraf": "3.0.2",
"sinon": "9.2.3",
"ts-mocha": "8.0.0",
"ts-node": "9.1.1",
"typescript": "3.9.7"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,18 @@ export class PrometheusExporter implements MetricExporter {
});
}

/**
* Request handler that responds with the current state of metrics
* @param request Incoming HTTP request of server instance
* @param response HTTP response objet used to response to request
*/
public getMetricsRequestHandler(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function names implies you are getting a handler, which is not the what is actually happening. I think it would be smarter to return a middleware that is compatible with express and other web frameworks:

public getMetricsRequestHandler() {
  const self = this;
  return function metricsHandler(req, res, next) {
	// req validation?
    // suppress instrumenting this method? optionally?
	// error handling?	

    self.exportMetrics(res);
  };
}

Copy link
Contributor Author

@weyert weyert Jan 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea I was having to return a raw handler which has the IncomingMessage, and ServerResponse arguments as we can't really be sure what the user's framework is. I was thinking the user could do something like this for Express:

app.get('/metrics', (req, res) => {
    return exporter.getMetricsRequestHandler(req, res)
})

while for Hapi you could do something like:

function HapiWrapper(exporter) {
    const handler = exporter.getMetricsRequestHandler()
    return async ( { raw }, h) {
         await handler(raw.req, raw.res)
         return h.close
    } 
}

while for Koa something like:

const router = app.router()
router.get('/metrics', async (ctx) => {
   await handle(ctx.req, ctx.res)
   ctx.respond = false
})

Probably would need to allow the handler to be async but the above the idea to offer most flexibility. Maybe the name needs to better reflect this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you have a point maybe it should be returned with .bind(this)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @weyert here, its better to use a simpler model that doesn't necessarely match express api so it can be used with every framework (at the cost of having a different usage)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @vmarchaud :)

_request: IncomingMessage,
response: ServerResponse
) {
this._exportMetrics(response);
}

/**
* Request handler used by http library to respond to incoming requests
* for the current state of metrics by the Prometheus backend.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ import {
HistogramAggregator,
} from '@opentelemetry/metrics';
import * as assert from 'assert';
import * as sinon from 'sinon';
import * as http from 'http';
import { PrometheusExporter } from '../src';
import { mockAggregator, mockedHrTimeMs } from './util';
import { SinonStubbedInstance } from 'sinon';

describe('PrometheusExporter', () => {
mockAggregator(SumAggregator);
Expand Down Expand Up @@ -171,6 +173,23 @@ describe('PrometheusExporter', () => {
return done();
});
});

it('should able to call getMetricsRequestHandler function to generate response with metrics', () => {
const exporter = new PrometheusExporter({ preventServerStart: true });
const mockRequest: SinonStubbedInstance<http.IncomingMessage> = sinon.createStubInstance(
http.IncomingMessage
);
const mockResponse: SinonStubbedInstance<http.ServerResponse> = sinon.createStubInstance(
http.ServerResponse
);
exporter.getMetricsRequestHandler(
(mockRequest as unknown) as http.IncomingMessage,
(mockResponse as unknown) as http.ServerResponse
);
sinon.assert.calledOnce(mockResponse.setHeader);
sinon.assert.calledOnce(mockResponse.end);
assert.strictEqual(mockResponse.statusCode, 200);
});
});

describe('export', () => {
Expand Down