-
Notifications
You must be signed in to change notification settings - Fork 237
feat: zero-configuration support by always successfully inferring a serviceName #2410
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
Conversation
…erviceName If a bogus serviceName is explicitly configured, the agent will still log.error and de-activate. However, if the agent is inferring a serviceName, then with this change it will always succeed. This change improves inference from a package.json by dealing with some cases where a package.json "name" was an invalid serviceName. And then ultimately it falls back to "nodejs_service" if a name cannot otherwise be inferred. This changes Agent configuration somewhat. Before this change the agent would `this._config()` twice: once at Agent creation (resulting in a agent._conf that is incorrect if options are later passed to agent.start()), and once at agent.start(). This commit changes to only configure at agent.start(). A separate `config.configLogger()` handles setting `agent.logger` to a valid early logger when the Agent is created. All Agent methods have been updated as necessary to guard against usage before the agent is started. One visible semantic change is that `agent.getServiceName()` will now always return `undefined` before the agent is started. Arguably the earlier behaviour was a subtle bug, but the returned serviceName could be *wrong* if the agent was later started with a given serviceName. Closes: #1944
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add review notes.
global[symbols.agentInitialized] = null | ||
|
||
if (Error.stackTraceLimit === this._conf.stackTraceLimit) { | ||
if (this._origStackTraceLimit && Error.stackTraceLimit !== this._origStackTraceLimit) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REVIEW NOTE: This is a guard against agent.destroy() being called before agent.start()
frameCache: frameCacheStats | ||
} | ||
if (typeof this._transport._getStats === 'function') { | ||
if (this._transport && typeof this._transport._getStats === 'function') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REVIEW NOTE: This is a guard against agent._getStats() being called before agent.start()
// The `uncaughtException` listener inhibits this behavior, and it's | ||
// therefore necessary to manually do this to not break expectations. | ||
if (agent._conf.logUncaughtExceptions === true) { | ||
if (agent._conf && agent._conf.logUncaughtExceptions === true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REVIEW NOTE: This is a guard against calling agent.handleUncaughtExceptions() and then capturing an uncaught exception before agent.start()
const { name, version } = readPkgUp.sync().packageJson | ||
serviceName = name | ||
serviceVersion = version | ||
} catch (err) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REVIEW NOTE: The equivalent of this was moved to getPkg()
below.
this._runCtxMgr = null | ||
|
||
this._log = agent.logger.child({ 'event.module': 'instrumentation' }) | ||
this._log = agent.logger |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REVIEW NOTE: There was no value in the event.module
field in the log records, so I dropped it rather than having to reproduce it in the Instrumentation.prototype.start
change below.
} else { | ||
logger.level = pinoLevel | ||
} | ||
logger.level = pinoLevel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REVIEW NOTE: This was an existing small bug that was exposed by testing the configLogger()
addition.
t.ok(!agent.isStarted(), 'agent should not have been started yet') | ||
t.strictEqual(agent.getServiceName(), packageJson.name) | ||
t.strictEqual(agent.getServiceName(), agent._conf.serviceName) | ||
t.strictEqual(agent.getServiceName(), undefined) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REVIEW NOTE: This is the semantic change that was mentioned in the PR description and CHANGELOG message.
t.strictEqual(agent._conf.serviceName, 'elastic-apm-node') | ||
agent.destroy() | ||
t.end() | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REVIEW NOTE: This test is a duplicate of "serviceName/serviceVersion zero-conf: valid" below.
t.end() | ||
}) | ||
|
||
test('serviceName defaults to package name', function (t) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REVIEW NOTE: This suite of test cases was replaced by the 'serviceName/serviceVersion zero-conf: ' test cases below. Instead of having all this goop to create directory trees and packages in which to test, I just created the various test scenarios in "test/fixtures/pkg-zero-conf-/" dirs.
require('..').start({ | ||
disableSend: true | ||
}) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REVIEW NOTE: This file and a few test files below implicitly depended on a configured agent because this code reaches into the agent singleton to get its config:
const agent = require('../..') |
💚 Build Succeeded
Expand to view the summary
Build stats
Test stats 🧪
🤖 GitHub commentsTo re-run your PR in the CI, just comment with:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of questions about potential improvments, but I'd be OK with this going in as is.
|
||
// Early configuration to ensure `agent.logger` works before `.start()` | ||
// is called. | ||
this._config() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to getting the agent a logger immediately upon instantiation. However, without this call to this._conf the agent will no longer have a configuration object between its initial module loading and the agent starting.
const agent = require('elastic-apm-node');
// will not be set
console.log(agent._conf);
agent.start({});
// only now is it set
console.log(agent._conf);
Are we OK breaking this not-explicit-but-a-bit-implicit API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am. ;)
My first justification is that this is an internal/undocumented property. We cannot, IMHO, in general have internal code changes be disallowed by possible/unknown external usage.
The main justification is that agent._conf
between Agent creation and agent.start(...)
is incomplete -- a stronger word is that this _conf is wrong and misleading. External code should not be making behaviour decisions based on a config value that will be different after agent.start(...)
. It could lead to a more subtle bug. (The obvious exception here is the logLevel, which we need for internal logging before agent.start()
. If a good use case for another exception comes up, I think having the specific use case would much better inform how we support it.)
The last justification is that I would be surprised if there is any meaningful usage of the Agent instance in customer code before agent.start(...)
is called.
If a bogus serviceName is explicitly configured, the agent will still
log.error and de-activate. However, if the agent is inferring a
serviceName, then with this change it will always succeed. This change
improves inference from a package.json by dealing with some cases where
a package.json "name" was an invalid serviceName. And then ultimately it
falls back to "nodejs_service" if a name cannot otherwise be inferred.
This changes Agent configuration somewhat. Before this change the agent
would
this._config()
twice: once at Agent creation (resulting in aagent._conf that is incorrect if options are later passed to
agent.start()), and once at agent.start(). This commit changes to only
configure at agent.start(). A separate
config.configLogger()
handlessetting
agent.logger
to a valid early logger when the Agent iscreated. All Agent methods have been updated as necessary to guard
against usage before the agent is started. One visible semantic change
is that
agent.getServiceName()
will now always returnundefined
before the agent is started. Arguably the earlier behaviour was a
subtle bug, but the returned serviceName could be wrong if the agent
was later started with a given serviceName.
Closes: #1944
Checklist