Skip to content

Commit 67ee736

Browse files
Added configuration option: GLOBAL_LABELS (#1007)
* GLOBAL_LABELS: Added documentation * GLOBAL_LABELS: Added as new option part 1 * Fixed use case when label/custom key is a numeric value string * GLOBAL_LABELS: Added as new option part 2 * GLOBAL_LABELS: Added unit and component tests * Added test for max keyword length * Fixed implementation for label key length above max keyword length * Fixed use case for numeric string label key * Simplified deserialization * Fixed failing ConfigSettingTest * Fixed failing ConfigSettingTest part 2 * Moved JsonUtil::decode to JsonUtilForTests * Fixed code formatting
1 parent f37b993 commit 67ee736

File tree

54 files changed

+882
-197
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+882
-197
lines changed

docs/configuration.asciidoc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,36 @@ See {apm-app-ref}/filters.html#environment-selector[environment selector] in the
380380
NOTE: This feature is fully supported in the APM app in Kibana versions >= 7.2.
381381
You must use the query bar to filter for a specific environment in versions prior to 7.2.
382382

383+
[float]
384+
[[config-global-labels]]
385+
==== `global_labels`
386+
387+
[options="header"]
388+
|============
389+
| Hostname variable name | Option name in `php.ini`
390+
| `ELASTIC_APM_GLOBAL_LABELS` | `elastic_apm.global_labels`
391+
|============
392+
393+
[options="header"]
394+
|============
395+
| Default | Type
396+
| empty map | string to string map
397+
|============
398+
399+
Labels from this configuration are added to all the entities produced by the agent.
400+
401+
The format is `key=value[,key=value[,...]]`.
402+
For example `dept=engineering,rack=number8`.
403+
404+
NOTE: When setting this configuration option in `.ini` file
405+
it is required to enclose the value in quotes (because the value contains equal sign).
406+
For example `elastic_apm.global_labels = "dept=engineering,rack=number8"`
407+
408+
Any labels set by the application via the agent's public API
409+
will override global labels with the same keys.
410+
411+
NOTE: This option requires APM Server 7.2 or later. It will have no effect on older versions.
412+
383413
[float]
384414
[[config-hostname]]
385415
==== `hostname`

src/ElasticApm/Impl/Config/AllOptionsMetadata.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public static function get(): array
9595
OptionNames::DISABLE_SEND => new BoolOptionMetadata(/* default */ false),
9696
OptionNames::ENABLED => new BoolOptionMetadata(/* default */ true),
9797
OptionNames::ENVIRONMENT => new NullableStringOptionMetadata(),
98+
OptionNames::GLOBAL_LABELS => new NullableLabelsOptionMetadata(),
9899
OptionNames::HOSTNAME => new NullableStringOptionMetadata(),
99100
OptionNames::LOG_LEVEL => new NullableLogLevelOptionMetadata(),
100101
OptionNames::LOG_LEVEL_STDERR => new NullableLogLevelOptionMetadata(),
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
/*
4+
* Licensed to Elasticsearch B.V. under one or more contributor
5+
* license agreements. See the NOTICE file distributed with
6+
* this work for additional information regarding copyright
7+
* ownership. Elasticsearch B.V. licenses this file to you under
8+
* the Apache License, Version 2.0 (the "License"); you may
9+
* not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
declare(strict_types=1);
23+
24+
namespace Elastic\Apm\Impl\Config;
25+
26+
use Elastic\Apm\Impl\Log\LoggableToString;
27+
use Elastic\Apm\Impl\Util\TextUtil;
28+
29+
/**
30+
* Code in this file is part of implementation internals and thus it is not covered by the backward compatibility.
31+
*
32+
* @internal
33+
*
34+
* @extends OptionParser<array<string>>
35+
*/
36+
final class KeyValuePairsOptionParser extends OptionParser
37+
{
38+
/**
39+
* @param string $rawValue
40+
*
41+
* @return array<string>
42+
*/
43+
public function parse(string $rawValue): array
44+
{
45+
// Value format:
46+
// key=value[,key=value[,...]]
47+
48+
// Treat empty string as zero key-value pairs
49+
if (TextUtil::isEmptyString($rawValue)) {
50+
return [];
51+
}
52+
53+
$pairs = explode(',', $rawValue);
54+
$result = [];
55+
foreach ($pairs as $keyValuePair) {
56+
$keyValueSeparatorPos = strpos($keyValuePair, '=');
57+
if ($keyValueSeparatorPos === false) {
58+
throw new ParseException('One of key-value pairs is missing key-value separator' . ' ;' . LoggableToString::convert(['keyValuePair' => $keyValuePair, 'rawValue' => $rawValue]));
59+
}
60+
$key = trim(substr($keyValuePair, /* offset */ 0, /* length */ $keyValueSeparatorPos));
61+
$value = ($keyValueSeparatorPos === (strlen($keyValuePair) - 1)) ? '' : trim(substr($keyValuePair, /* offset */ $keyValueSeparatorPos + 1));
62+
if (array_key_exists($key, $result)) {
63+
throw new ParseException(
64+
'Key is present more than once'
65+
. ' ;' . LoggableToString::convert(['key' => $key, '1st value' => $result[$key], '2nd value' => $value, '2nd keyValuePair' => $keyValuePair, 'rawValue' => $rawValue])
66+
);
67+
}
68+
$result[$key] = $value;
69+
}
70+
return $result;
71+
}
72+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
/*
4+
* Licensed to Elasticsearch B.V. under one or more contributor
5+
* license agreements. See the NOTICE file distributed with
6+
* this work for additional information regarding copyright
7+
* ownership. Elasticsearch B.V. licenses this file to you under
8+
* the Apache License, Version 2.0 (the "License"); you may
9+
* not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
declare(strict_types=1);
23+
24+
namespace Elastic\Apm\Impl\Config;
25+
26+
use Elastic\Apm\Impl\Tracer;
27+
28+
/**
29+
* Code in this file is part of implementation internals and thus it is not covered by the backward compatibility.
30+
*
31+
* @internal
32+
*
33+
* @extends OptionParser<array<string|bool|int|float|null>>
34+
*/
35+
final class LabelsOptionParser extends OptionParser
36+
{
37+
/**
38+
* @param string $valueAsString
39+
*
40+
* @return string|bool|int|float|null
41+
*/
42+
private static function parseValue(string $valueAsString)
43+
{
44+
if ($valueAsString === 'true') {
45+
return true;
46+
}
47+
if ($valueAsString === 'false') {
48+
return false;
49+
}
50+
51+
if (filter_var($valueAsString, FILTER_VALIDATE_INT) !== false) {
52+
return intval($valueAsString);
53+
}
54+
55+
if (filter_var($valueAsString, FILTER_VALIDATE_FLOAT) !== false) {
56+
return floatval($valueAsString);
57+
}
58+
59+
if ($valueAsString === 'null') {
60+
return null;
61+
}
62+
63+
return Tracer::limitKeywordString($valueAsString);
64+
}
65+
66+
/**
67+
* @param string $rawValue
68+
*
69+
* @return array<string|bool|int|float|null>
70+
*/
71+
public function parse(string $rawValue): array
72+
{
73+
// Value format:
74+
// key=value[,key=value[,...]]
75+
76+
$result = [];
77+
foreach ((new KeyValuePairsOptionParser())->parse($rawValue) as $key => $valueAsString) {
78+
$result[is_string($key) ? Tracer::limitKeywordString($key) : $key] = self::parseValue($valueAsString);
79+
}
80+
return $result;
81+
}
82+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* Licensed to Elasticsearch B.V. under one or more contributor
5+
* license agreements. See the NOTICE file distributed with
6+
* this work for additional information regarding copyright
7+
* ownership. Elasticsearch B.V. licenses this file to you under
8+
* the Apache License, Version 2.0 (the "License"); you may
9+
* not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
declare(strict_types=1);
23+
24+
namespace Elastic\Apm\Impl\Config;
25+
26+
/**
27+
* Code in this file is part of implementation internals and thus it is not covered by the backward compatibility.
28+
*
29+
* @internal
30+
*
31+
* @extends NullableOptionMetadata<array<string|bool|int|float|null>>
32+
*
33+
* @noinspection PhpUnused
34+
*/
35+
final class NullableLabelsOptionMetadata extends NullableOptionMetadata
36+
{
37+
public function __construct()
38+
{
39+
parent::__construct(new LabelsOptionParser());
40+
}
41+
}

src/ElasticApm/Impl/Config/OptionNames.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ final class OptionNames
4747
public const DISABLE_SEND = 'disable_send';
4848
public const ENABLED = 'enabled';
4949
public const ENVIRONMENT = 'environment';
50+
public const GLOBAL_LABELS = 'global_labels';
5051
public const HOSTNAME = 'hostname';
5152
public const INTERNAL_CHECKS_LEVEL = 'internal_checks_level';
5253
public const LOG_LEVEL = 'log_level';

src/ElasticApm/Impl/Config/Snapshot.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ final class Snapshot implements LoggableInterface
137137
/** @var ?string */
138138
private $environment;
139139

140+
/** @var ?array<string|bool|int|float|null> */
141+
private $globalLabels;
142+
140143
/** @var ?string */
141144
private $hostname;
142145

@@ -309,6 +312,14 @@ public function environment(): ?string
309312
return $this->environment;
310313
}
311314

315+
/**
316+
* @return ?array<string|bool|int|float|null>
317+
*/
318+
public function globalLabels(): ?array
319+
{
320+
return $this->globalLabels;
321+
}
322+
312323
public function hostname(): ?string
313324
{
314325
return $this->hostname;

src/ElasticApm/Impl/ExecutionSegmentContext.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ public function jsonSerialize()
117117
// APM Server Intake API expects 'tags' key for labels
118118
// https://github.com/elastic/apm-server/blob/7.0/docs/spec/context.json#L46
119119
// https://github.com/elastic/apm-server/blob/7.0/docs/spec/spans/span.json#L88
120-
if ($this->labels !== null) {
121-
SerializationUtil::addNameValueIfNotEmpty('tags', (object)$this->labels, /* ref */ $result);
120+
if ($this->labels !== null && !ArrayUtil::isEmpty($this->labels)) {
121+
SerializationUtil::addNameValue('tags', (object)$this->labels, /* ref */ $result);
122122
}
123123

124124
return SerializationUtil::postProcessResult($result);

src/ElasticApm/Impl/Log/LoggableToJsonEncodable.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ private static function convertSmallMixedKeysMapArray(array $mapArrayValue, int
229229
{
230230
$result = [];
231231
foreach ($mapArrayValue as $key => $value) {
232-
$result[] = [self::convert($key, $depth), self::convert($value, $depth)];
232+
$result[] = ['key' => self::convert($key, $depth), 'value' => self::convert($value, $depth)];
233233
}
234234
return $result;
235235
}

src/ElasticApm/Impl/Metadata.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Elastic\Apm\Impl\BackendComm\SerializationUtil;
2727
use Elastic\Apm\Impl\Log\LoggableInterface;
2828
use Elastic\Apm\Impl\Log\LoggableTrait;
29+
use Elastic\Apm\Impl\Util\ArrayUtil;
2930

3031
/**
3132
* Code in this file is part of implementation internals and thus it is not covered by the backward compatibility.
@@ -36,6 +37,13 @@ final class Metadata implements SerializableDataInterface, LoggableInterface
3637
{
3738
use LoggableTrait;
3839

40+
/**
41+
* @var ?array<string|bool|int|float|null>
42+
*
43+
* @link https://github.com/elastic/apm-server/blob/v7.2.0/docs/spec/metadata.json#L32C10-L32C16
44+
*/
45+
public $labels = null;
46+
3947
/**
4048
* @var ProcessData
4149
*
@@ -65,6 +73,9 @@ public function jsonSerialize()
6573
{
6674
$result = [];
6775

76+
if ($this->labels !== null && !ArrayUtil::isEmpty($this->labels)) {
77+
SerializationUtil::addNameValue('labels', (object)$this->labels, /* ref */ $result);
78+
}
6879
SerializationUtil::addNameValue('process', $this->process, /* ref */ $result);
6980
SerializationUtil::addNameValue('service', $this->service, /* ref */ $result);
7081
SerializationUtil::addNameValue('system', $this->system, /* ref */ $result);

0 commit comments

Comments
 (0)