Skip to content

Commit

Permalink
feat: add longFieldMaxLength config option, deprecate `errorMessage…
Browse files Browse the repository at this point in the history
…MaxLength` (#2193)

This config option will truncate some transaction, span, and error fields
that can be longer at the configured number of JavaScript characters per
https://github.com/elastic/apm/blob/master/specs/agents/field-limits.md#long_field_max_length-configuration

Notably truncation is now based on a count of JavaScript *characters*
rather than in UTF-8 encoded bytes.

This also deprecates the errorMessageMaxLength config option. It now
defaults to the longFieldMaxLength value if not specified. Otherwise
errorMessageMaxLength is still supported.

Fixes: #1921
  • Loading branch information
trentm authored Sep 3, 2021
1 parent 836ab1b commit ec719c2
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 8 deletions.
25 changes: 25 additions & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,31 @@ Notes:
[float]
===== Features
* Add the `longFieldMaxLength` integer configuration option (default `10000`).
Specific transaction/span/error fields (see the list below) will be truncated
at this number of unicode characters. ({pull}2193[#2193], {issues}1921[#1921])
+
The `errorMessageMaxLength` configuration option is now *deprecated*, but
still supported. Users should switch to using `longFieldMaxLength. If
`errorMessageMaxLength` is not specified, truncation of error messages will
now use the `longFieldMaxLength` value.
+
Note that ultimately the maximum length of any tracing field is limited by the
{apm-server-ref-v}/configuration-process.html#max_event_size[`max_event_size`]
configured for the receiving APM server.
+
The fields affected by `longFieldMaxLength` are:
+
** `transaction.context.request.body`, `error.context.request.body` - Before
this change these fields were not truncated.
** `transaction.context.message.body`, `span.context.message.body`,
`error.context.message.body` - Before this change these fields were not
truncated.
** `span.context.db.statement` - Before this change this field was truncated
at 10000 *bytes*. Truncation is now a number of unicode characters.
** `error.exception.message`, `error.log.message` - Before this change, the
default 2kB `errorMessageMaxLength` would apply.
* Improve the TypeScript types by exporting more of interfaces:
`AgentConfigOptions`, `Transaction`, `Span`, `TransactionOptions`,
`SpanOptions`. ({issues}2118[#2118])
Expand Down
31 changes: 30 additions & 1 deletion docs/configuration.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -671,16 +671,20 @@ The default value `0` means that no source code will be collected for span libra
==== `errorMessageMaxLength`

* *Type:* String
* *Default:* `2kb`
* *Default:* `"2kb"`
* *Env:* `ELASTIC_APM_ERROR_MESSAGE_MAX_LENGTH`

This option is **deprecated** -- use <<long-field-max-length,`longFieldMaxLength`>> instead.

The maximum length allowed for error messages.
It is expressed in bytes or includes a size suffix such as `2kb`.
Size suffixes are case-insensitive and include `b`,
`kb`,
`mb`,
and `gb`.
Messages above this length will be truncated before being sent to the APM Server.
Note that while the configuration option accepts a number of *bytes*, truncation
is based on a number of unicode characters, not bytes.

Set to `-1` do disable truncation.

Expand All @@ -689,6 +693,31 @@ This applies to the following properties:
- `error.exception.message`
- `error.log.message`


[[long-field-max-length]]
==== `longFieldMaxLength`

* *Type:* Integer
* *Default:* 10000
* *Env:* `ELASTIC_APM_LONG_FIELD_MAX_LENGTH`

The following transaction, span, and error fields will be truncated at this
number of unicode characters before being sent to APM server:

- `transaction.context.request.body`, `error.context.request.body`
- `transaction.context.message.body`, `span.context.message.body`,
`error.context.message.body`
- `span.context.db.statement`
- `error.exception.message`, `error.log.message` - If
<<error-message-max-length,`errorMessageMaxLength`>> is specified, then that
value takes precedence for these error message fields.

Note that tracing data is limited at the upstream APM server to
{apm-server-ref-v}/configuration-process.html#max_event_size[`max_event_size`],
which defaults to 300kB. If you configure `longFieldMaxLength` too large, it
could result in transactions, spans, or errors that are rejected by APM server.


[[stack-trace-limit]]
==== `stackTraceLimit`

Expand Down
3 changes: 2 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ declare namespace apm {
disableInstrumentations?: string | string[];
disableSend?: boolean;
environment?: string;
errorMessageMaxLength?: string; // Also support `number`, but as we're removing this functionality soon, there's no need to advertise it
errorMessageMaxLength?: string; // DEPRECATED: use `longFieldMaxLength`.
errorOnAbortedRequests?: boolean;
filterHttpHeaders?: boolean;
frameworkName?: string;
Expand All @@ -237,6 +237,7 @@ declare namespace apm {
logLevel?: LogLevel;
logUncaughtExceptions?: boolean;
logger?: PinoLogger | Logger;
longFieldMaxLength?: number;
maxQueueSize?: number;
metricsInterval?: string; // Also support `number`, but as we're removing this functionality soon, there's no need to advertise it
metricsLimit?: number;
Expand Down
19 changes: 16 additions & 3 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ var DEFAULTS = {
disableInstrumentations: [],
disableSend: false,
environment: process.env.NODE_ENV || 'development',
errorMessageMaxLength: '2kb',
errorOnAbortedRequests: false,
filterHttpHeaders: true,
globalLabels: undefined,
Expand All @@ -70,6 +69,7 @@ var DEFAULTS = {
logUncaughtExceptions: false, // TODO: Change to `true` in the v4.0.0
// Rough equivalent of the Java Agent's max_queue_size:
// https://www.elastic.co/guide/en/apm/agent/java/current/config-reporter.html#config-max-queue-size
longFieldMaxLength: 10000,
maxQueueSize: 1024,
metricsInterval: '30s',
metricsLimit: 1000,
Expand Down Expand Up @@ -129,6 +129,7 @@ var ENV_TABLE = {
kubernetesPodUID: ['ELASTIC_APM_KUBERNETES_POD_UID', 'KUBERNETES_POD_UID'],
logLevel: 'ELASTIC_APM_LOG_LEVEL',
logUncaughtExceptions: 'ELASTIC_APM_LOG_UNCAUGHT_EXCEPTIONS',
longFieldMaxLength: 'ELASTIC_APM_LONG_FIELD_MAX_LENGTH',
maxQueueSize: 'ELASTIC_APM_MAX_QUEUE_SIZE',
metricsInterval: 'ELASTIC_APM_METRICS_INTERVAL',
metricsLimit: 'ELASTIC_APM_METRICS_LIMIT',
Expand Down Expand Up @@ -184,6 +185,7 @@ var BOOL_OPTS = [
]

var NUM_OPTS = [
'longFieldMaxLength',
'maxQueueSize',
'metricsLimit',
'sourceLinesErrorAppFrames',
Expand Down Expand Up @@ -564,6 +566,7 @@ function truncateOptions (opts) {
if (opts.hostname) opts.hostname = truncate(String(opts.hostname), INTAKE_STRING_MAX_SIZE)
}

// Translate a string byte size, e.g. '10kb', into an integer number of bytes.
function bytes (input) {
const matches = input.match(/^(\d+)(b|kb|mb|gb)$/i)
if (!matches) return Number(input)
Expand Down Expand Up @@ -670,7 +673,8 @@ function getBaseClientConfig (conf, agent) {
if (isLambdaExecutionEnviornment()) {
cloudProvider = 'none'
}
return {

const clientConfig = {
agentName: 'nodejs',
agentVersion: version,
serviceName: conf.serviceName,
Expand All @@ -684,7 +688,8 @@ function getBaseClientConfig (conf, agent) {

// Sanitize conf
truncateKeywordsAt: INTAKE_STRING_MAX_SIZE,
truncateErrorMessagesAt: conf.errorMessageMaxLength,
truncateLongFieldsAt: conf.longFieldMaxLength,
// truncateErrorMessagesAt: see below

// HTTP conf
secretToken: conf.secretToken,
Expand Down Expand Up @@ -717,6 +722,14 @@ function getBaseClientConfig (conf, agent) {
// Cloud metadata fetching
cloudMetadataFetcher: (new CloudMetadata(cloudProvider, conf.logger, conf.serviceName))
}

if (conf.errorMessageMaxLength !== undefined) {
// As of v10 of the http client, truncation of error messages will default
// to `truncateLongFieldsAt` if `truncateErrorMessagesAt` is not specified.
clientConfig.truncateErrorMessagesAt = conf.errorMessageMaxLength
}

return clientConfig
}

// Exports.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
"basic-auth": "^2.0.1",
"cookie": "^0.4.0",
"core-util-is": "^1.0.2",
"elastic-apm-http-client": "^9.9.0",
"elastic-apm-http-client": "^10.0.0",
"end-of-stream": "^1.4.4",
"error-callsites": "^2.0.4",
"error-stack-parser": "^2.0.6",
Expand Down
5 changes: 3 additions & 2 deletions test/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ var optionFixtures = [
['disableSend', 'DISABLE_SEND', false],
['disableInstrumentations', 'DISABLE_INSTRUMENTATIONS', []],
['environment', 'ENVIRONMENT', 'development'],
['errorMessageMaxLength', 'ERROR_MESSAGE_MAX_LENGTH', 2048],
['errorMessageMaxLength', 'ERROR_MESSAGE_MAX_LENGTH', undefined],
['errorOnAbortedRequests', 'ERROR_ON_ABORTED_REQUESTS', false],
['filterHttpHeaders', 'FILTER_HTTP_HEADERS', true],
['frameworkName', 'FRAMEWORK_NAME'],
Expand All @@ -74,6 +74,7 @@ var optionFixtures = [
['kubernetesPodUID', 'KUBERNETES_POD_UID'],
['logLevel', 'LOG_LEVEL', 'info'],
['logUncaughtExceptions', 'LOG_UNCAUGHT_EXCEPTIONS', false],
['longFieldMaxLength', 'LONG_FIELD_MAX_LENGTH', 10000],
['maxQueueSize', 'MAX_QUEUE_SIZE', 1024],
['metricsInterval', 'METRICS_INTERVAL', 30],
['metricsLimit', 'METRICS_LIMIT', 1000],
Expand Down Expand Up @@ -109,7 +110,7 @@ optionFixtures.forEach(function (fixture) {
} else if (fixture[0] === 'serverCaCertFile') {
// special case for files, so a temp file can be written
type = 'file'
} else if (typeof fixture[2] === 'number') {
} else if (typeof fixture[2] === 'number' || fixture[0] === 'errorMessageMaxLength') {
type = 'number'
} else if (Array.isArray(fixture[2])) {
type = 'array'
Expand Down

0 comments on commit ec719c2

Please sign in to comment.