Help building a New Relic APM module

All of our application monitoring is currently done via New Relic APM, so naturally we want to be able to monitor Caddy via New Relic APM as well. I’m in the process of trying to build a Caddy New Relic plugin, and one problem I’ve run into is how to know which request matcher/named route matched a given request.

For example, let’s say my Caddyfile has a route like this:

path /v1/users/*

If you were instrumenting any other Go-based web app/API, you’d do something like this…

http.HandleFunc(newrelic.WrapHandleFunc(app, "/v1/users/:id", usersHandler))

…so that all requests to this endpoint (for all users) get treated as a single transaction within New Relic, and not a transaction for each individual user (i.e. /v1/users/1, /v1/users/2, etc, which would cause the number of distinct transactions within New Relic to balloon based on the number of distinct users).

To do this within Caddy, my initial thought was to simply use the request matcher/route as the name of the transaction. Is there anything available to modules in the http.handlers namespace that tells the middleware handler which request matcher/route matched the current request? If not, could someone point me in the direction of where/how I would make that info available to caddyhttp.MiddlewareHandler middleware? Or, if I’m simply thinking about this wrong and there’s a better way to do this, I’m all ears. :slight_smile:

Thanks in advance.

We don’t do any tracking of request matchers, currently.

For your usecase, it might make sense to set up a handler directive which can set a marker in the request’s context with the name/ID for that route.

Might look something like this:

handle /v2/users/* {
	newrelic /v2/users
	reverse_proxy your-app:8080
}

handle /v1/users/* {
	newrelic /v1/users
	reverse_proxy your-app:8080
}

Or something to that effect.

That’s the way it’s done with the tracing directive which is used for OpenTelemetry tracing (Caddyfile directive) — Caddy Documentation

Since it’s a middleware chain, if the newrelic directive is ordered higher than anything in that handle/route (you can ensure that by using the global option order newrelic first), then wrapping the handler function before calling the next one might have the same effect as your code snippet.

Thanks @francislavoie . That makes sense, but I think would fall apart if you’re using named matchers that contain multiple paths like this:

@user_service_paths {
	path /v1/users/search
	path /v1/users/*/transactions
	path /v1/users/*
	......etc......
}
route @user_service_paths {
	reverse_proxy {$USER_SERVICE_URI}
}

This is why I was wondering if there was a way to update Caddy so that whatever component is responsible for deciding which of those path matchers “wins” and matches the incoming request, it could set something on the request object so downstream middleware know which request matcher was selected for the incoming request. I just don’t know where in the Caddy codebase this route matching logic lives.

Request matchers are by-design “dumb”. They just return a boolean result. No side effects (except in some edge cases where they do have side effects like adding some placeholders to the request context).

There’s many kinds of matchers, and many of them don’t have an easy way to generate an identifier for the match. For path matching it’s pretty obvious, just use the match pattern string. But for things like header matchers where it’s a key-value match, or regexp matching, or file matching, or even expression matching… it gets a lot trickier. So I’m not sure there’s really a good option for generalizing this.

Most of the matchers are here in code (some others like the file matcher is in another package, look for MatchFile if you care):

2 Likes

Yup, great points. Ok, will think about it some more and try to work my way through the Caddy codebase to see if I can come up with a solution to this problem. Thanks again @francislavoie for your help.

Oh – not to mention some matches are AND, if you used a named matcher with two different kinds of matchers for example, so it’s not sufficient to say “it’s just this one path” because that would be a lie :sweat_smile:

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.