@redthreadlabs/tracelog 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +16 -0
- package/lib/agent.js +21 -1
- package/lib/apm-client/apm-client.js +1 -1
- package/lib/apm-client/channel-writer.js +1 -1
- package/lib/apm-client/jsonl-file-client.js +1 -1
- package/lib/apm-client/s3-uploader.js +1 -1
- package/lib/config/config.js +2 -0
- package/lib/config/normalizers.js +41 -0
- package/lib/config/schema.js +11 -0
- package/lib/instrumentation/index.js +76 -2
- package/lib/metrics/reporter.js +13 -1
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -293,6 +293,15 @@ declare namespace apm {
|
|
|
293
293
|
spanStackTraceMinDuration?: string;
|
|
294
294
|
stackTraceLimit?: number;
|
|
295
295
|
traceContinuationStrategy?: TraceContinuationStrategy;
|
|
296
|
+
/**
|
|
297
|
+
* Route auto-instrumented transactions (and their spans and breakdown
|
|
298
|
+
* metricsets) to a named channel when the transaction name matches a
|
|
299
|
+
* wildcard pattern. First matching rule wins; unmatched records go to
|
|
300
|
+
* the default channel.
|
|
301
|
+
*
|
|
302
|
+
* Example: `[{ pattern: '* unknown route*', channel: 'unknown-route' }]`
|
|
303
|
+
*/
|
|
304
|
+
transactionChannels?: Array<TransactionChannelRule>;
|
|
296
305
|
transactionIgnoreUrls?: Array<string>;
|
|
297
306
|
transactionMaxSpans?: number;
|
|
298
307
|
transactionSampleRate?: number;
|
|
@@ -329,6 +338,13 @@ declare namespace apm {
|
|
|
329
338
|
writeSpan (span: object): void;
|
|
330
339
|
}
|
|
331
340
|
|
|
341
|
+
interface TransactionChannelRule {
|
|
342
|
+
/** Wildcard pattern matched against the transaction name. */
|
|
343
|
+
pattern: string;
|
|
344
|
+
/** Channel to route matching transactions, spans, and breakdown metricsets to. */
|
|
345
|
+
channel: string;
|
|
346
|
+
}
|
|
347
|
+
|
|
332
348
|
interface CaptureEventOptions {
|
|
333
349
|
message?: string;
|
|
334
350
|
level?: 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
package/lib/agent.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright Red Thread Labs LLC. and Elasticsearch B.V. and other contributors where applicable.
|
|
3
3
|
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
4
|
* compliance with the BSD 2-Clause License.
|
|
5
5
|
*/
|
|
@@ -831,6 +831,26 @@ Agent.prototype.writeEvents = function (events, cb) {
|
|
|
831
831
|
// the default channel ('server'). Named channels are created lazily via
|
|
832
832
|
// getChannel(name) and write to separate files (e.g. tracelog-client-*.jsonl).
|
|
833
833
|
|
|
834
|
+
/**
|
|
835
|
+
* Resolve the channel that records for the named transaction should be
|
|
836
|
+
* routed to, per the `transactionChannels` config rules (first matching
|
|
837
|
+
* rule wins). Returns null when no rule matches, i.e. the record belongs
|
|
838
|
+
* on the default channel. Used for auto-instrumented transactions, their
|
|
839
|
+
* spans, and their breakdown metricsets.
|
|
840
|
+
*/
|
|
841
|
+
Agent.prototype._channelForTransactionName = function (name) {
|
|
842
|
+
const rules = this._conf && this._conf.transactionChannelRules;
|
|
843
|
+
if (!rules || rules.length === 0 || typeof name !== 'string') {
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
for (let i = 0; i < rules.length; i++) {
|
|
847
|
+
if (rules[i].re.test(name)) {
|
|
848
|
+
return rules[i].channel;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return null;
|
|
852
|
+
};
|
|
853
|
+
|
|
834
854
|
/**
|
|
835
855
|
* Get a named channel. Returns an object with writeEvent, writeEvents,
|
|
836
856
|
* writeError, writeTransaction, and writeSpan methods that route to the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
-
* Copyright
|
|
3
|
+
* Copyright Red Thread Labs LLC. All rights reserved.
|
|
4
4
|
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
5
5
|
* compliance with the BSD 2-Clause License.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
-
* Copyright
|
|
3
|
+
* Copyright Red Thread Labs LLC. All rights reserved.
|
|
4
4
|
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
5
5
|
* compliance with the BSD 2-Clause License.
|
|
6
6
|
*/
|
package/lib/config/config.js
CHANGED
|
@@ -48,6 +48,7 @@ const {
|
|
|
48
48
|
normalizeSanitizeFieldNames,
|
|
49
49
|
normalizeCloudProvider,
|
|
50
50
|
normalizeCustomMetricsHistogramBoundaries,
|
|
51
|
+
normalizeTransactionChannels,
|
|
51
52
|
normalizeTransactionSampleRate,
|
|
52
53
|
normalizeTraceContinuationStrategy,
|
|
53
54
|
normalizeContextManager,
|
|
@@ -396,6 +397,7 @@ function normalize(opts, logger) {
|
|
|
396
397
|
normalizeDurationOptions(opts, DURATION_OPTS, defaults, logger);
|
|
397
398
|
normalizeBools(opts, BOOL_OPTS, defaults, logger);
|
|
398
399
|
normalizeIgnoreOptions(opts);
|
|
400
|
+
normalizeTransactionChannels(opts, [], defaults, logger);
|
|
399
401
|
normalizeElasticsearchCaptureBodyUrls(opts);
|
|
400
402
|
normalizeDisableMetrics(opts);
|
|
401
403
|
normalizeSanitizeFieldNames(opts);
|
|
@@ -427,6 +427,46 @@ function normalizeIgnoreOptions(opts, fields, defaults, logger) {
|
|
|
427
427
|
}
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
+
/**
|
|
431
|
+
* Normalizes transactionChannels rules into compiled wildcard matchers.
|
|
432
|
+
*
|
|
433
|
+
* Input is an array of { pattern, channel } objects, where `pattern` is a
|
|
434
|
+
* wildcard expression matched against the transaction name (e.g.
|
|
435
|
+
* '* unknown route*') and `channel` is the name of the channel to route
|
|
436
|
+
* matching transactions (and their spans and breakdown metricsets) to.
|
|
437
|
+
* Compiles to opts.transactionChannelRules: [{ re, channel }]. Invalid
|
|
438
|
+
* rules are dropped with a warning.
|
|
439
|
+
*
|
|
440
|
+
* @param {Record<String, unknown>} opts the configuration options to normalize
|
|
441
|
+
* @param {String[]} fields the list of fields to normalize (unused)
|
|
442
|
+
* @param {Record<String, unknown>} defaults the configuration defaults (unused)
|
|
443
|
+
* @param {import('../logging.js').Logger} logger
|
|
444
|
+
*/
|
|
445
|
+
function normalizeTransactionChannels(opts, fields, defaults, logger) {
|
|
446
|
+
if (opts.transactionChannels) {
|
|
447
|
+
opts.transactionChannelRules = [];
|
|
448
|
+
const wildcard = new WildcardMatcher();
|
|
449
|
+
for (const rule of opts.transactionChannels) {
|
|
450
|
+
if (
|
|
451
|
+
!rule ||
|
|
452
|
+
typeof rule.pattern !== 'string' ||
|
|
453
|
+
typeof rule.channel !== 'string' ||
|
|
454
|
+
!rule.channel
|
|
455
|
+
) {
|
|
456
|
+
logger.warn(
|
|
457
|
+
'invalid transactionChannels rule (expected { pattern, channel } strings): %j',
|
|
458
|
+
rule,
|
|
459
|
+
);
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
opts.transactionChannelRules.push({
|
|
463
|
+
re: wildcard.compile(rule.pattern),
|
|
464
|
+
channel: rule.channel,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
430
470
|
/**
|
|
431
471
|
* Normalizes the wildcard matchers of sanitizeFieldNames and thansforms the into RegExps
|
|
432
472
|
*
|
|
@@ -640,6 +680,7 @@ module.exports = {
|
|
|
640
680
|
normalizeKeyValuePairs,
|
|
641
681
|
normalizeNumbers,
|
|
642
682
|
normalizeSanitizeFieldNames,
|
|
683
|
+
normalizeTransactionChannels,
|
|
643
684
|
normalizeTransactionSampleRate,
|
|
644
685
|
secondsFromDuration,
|
|
645
686
|
normalizeTraceContinuationStrategy,
|
package/lib/config/schema.js
CHANGED
|
@@ -361,6 +361,17 @@ const CONFIG_SCHEMA = [
|
|
|
361
361
|
centralConfigName: 'trace_continuation_strategy',
|
|
362
362
|
crossAgentName: 'trace_continuation_strategy',
|
|
363
363
|
},
|
|
364
|
+
{
|
|
365
|
+
name: 'transactionChannels',
|
|
366
|
+
configType: 'transactionChannelArray',
|
|
367
|
+
defaultValue: [],
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
name: 'transactionChannelRules',
|
|
371
|
+
configType: 'wildcardChannelRuleArray',
|
|
372
|
+
defaultValue: [],
|
|
373
|
+
deps: ['transactionChannels'],
|
|
374
|
+
},
|
|
364
375
|
{
|
|
365
376
|
name: 'transactionIgnoreUrls',
|
|
366
377
|
configType: 'stringArray',
|
|
@@ -710,6 +710,14 @@ Instrumentation.prototype.addEndedTransaction = function (transaction) {
|
|
|
710
710
|
trans: transaction.id,
|
|
711
711
|
trace: transaction.traceId,
|
|
712
712
|
});
|
|
713
|
+
// The transaction's name is final now, so held spans can be routed
|
|
714
|
+
// even though the transaction record itself was dropped.
|
|
715
|
+
this._drainPendingChannelSpans(
|
|
716
|
+
transaction,
|
|
717
|
+
agent._channelForTransactionName(
|
|
718
|
+
transaction._customName || transaction._defaultName,
|
|
719
|
+
),
|
|
720
|
+
);
|
|
713
721
|
return;
|
|
714
722
|
}
|
|
715
723
|
|
|
@@ -717,7 +725,40 @@ Instrumentation.prototype.addEndedTransaction = function (transaction) {
|
|
|
717
725
|
trans: transaction.id,
|
|
718
726
|
trace: transaction.traceId,
|
|
719
727
|
});
|
|
720
|
-
agent.
|
|
728
|
+
const channel = agent._channelForTransactionName(payload.name);
|
|
729
|
+
// Spans held while this transaction's name was unresolved follow the
|
|
730
|
+
// same routing decision as the transaction itself.
|
|
731
|
+
this._drainPendingChannelSpans(transaction, channel);
|
|
732
|
+
if (channel && typeof agent._apmClient.sendToChannel === 'function') {
|
|
733
|
+
agent._apmClient.sendToChannel(channel, 'transaction', payload);
|
|
734
|
+
} else {
|
|
735
|
+
agent._apmClient.sendTransaction(payload);
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
// Send any span payloads that were held on the transaction because the
|
|
740
|
+
// transaction's name (and so its channel routing) was not yet resolved
|
|
741
|
+
// when the span ended. See `_encodeAndSendSpan`.
|
|
742
|
+
Instrumentation.prototype._drainPendingChannelSpans = function (
|
|
743
|
+
transaction,
|
|
744
|
+
channel,
|
|
745
|
+
) {
|
|
746
|
+
const agent = this._agent;
|
|
747
|
+
const pending = transaction._pendingChannelSpans;
|
|
748
|
+
if (!pending) {
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
transaction._pendingChannelSpans = null;
|
|
752
|
+
if (!agent._apmClient) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
for (const payload of pending) {
|
|
756
|
+
if (channel && typeof agent._apmClient.sendToChannel === 'function') {
|
|
757
|
+
agent._apmClient.sendToChannel(channel, 'span', payload);
|
|
758
|
+
} else {
|
|
759
|
+
agent._apmClient.sendSpan(payload);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
721
762
|
};
|
|
722
763
|
|
|
723
764
|
Instrumentation.prototype.addEndedSpan = function (span) {
|
|
@@ -841,7 +882,40 @@ Instrumentation.prototype._encodeAndSendSpan = function (span) {
|
|
|
841
882
|
type: span.type,
|
|
842
883
|
});
|
|
843
884
|
if (agent._apmClient) {
|
|
844
|
-
|
|
885
|
+
// Spans follow their transaction's channel. The transaction name
|
|
886
|
+
// is only meaningful for routing once *resolved* (custom or
|
|
887
|
+
// framework-set); the `name` getter must not be used here, since
|
|
888
|
+
// until the transaction ends it falls back to
|
|
889
|
+
// '<METHOD> unknown route (unnamed)', which would spuriously
|
|
890
|
+
// match rules aimed at unmatched-route traffic. When routing is
|
|
891
|
+
// configured and the name is not yet resolved (e.g. Express only
|
|
892
|
+
// names transactions when the request finishes), hold the span on
|
|
893
|
+
// its transaction; addEndedTransaction() drains held spans with
|
|
894
|
+
// the transaction's final routing decision.
|
|
895
|
+
const trans = span.transaction;
|
|
896
|
+
const routingEnabled =
|
|
897
|
+
agent._conf.transactionChannelRules &&
|
|
898
|
+
agent._conf.transactionChannelRules.length > 0 &&
|
|
899
|
+
typeof agent._apmClient.sendToChannel === 'function';
|
|
900
|
+
if (!routingEnabled) {
|
|
901
|
+
agent._apmClient.sendSpan(payload);
|
|
902
|
+
} else {
|
|
903
|
+
const resolvedName =
|
|
904
|
+
trans && (trans._customName || trans._defaultName);
|
|
905
|
+
if (!resolvedName && trans && !trans.ended) {
|
|
906
|
+
if (!trans._pendingChannelSpans) {
|
|
907
|
+
trans._pendingChannelSpans = [];
|
|
908
|
+
}
|
|
909
|
+
trans._pendingChannelSpans.push(payload);
|
|
910
|
+
} else {
|
|
911
|
+
const channel = agent._channelForTransactionName(resolvedName);
|
|
912
|
+
if (channel) {
|
|
913
|
+
agent._apmClient.sendToChannel(channel, 'span', payload);
|
|
914
|
+
} else {
|
|
915
|
+
agent._apmClient.sendSpan(payload);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
845
919
|
}
|
|
846
920
|
}
|
|
847
921
|
}
|
package/lib/metrics/reporter.js
CHANGED
|
@@ -67,7 +67,19 @@ class MetricsReporter extends Reporter {
|
|
|
67
67
|
|
|
68
68
|
if (this._agent._apmClient) {
|
|
69
69
|
for (const metric of seen.values()) {
|
|
70
|
-
|
|
70
|
+
// Breakdown metricsets carry the name of the transaction they
|
|
71
|
+
// were aggregated for; route them to that transaction's channel.
|
|
72
|
+
const channel = this._agent._channelForTransactionName(
|
|
73
|
+
metric.transaction && metric.transaction.name,
|
|
74
|
+
);
|
|
75
|
+
if (
|
|
76
|
+
channel &&
|
|
77
|
+
typeof this._agent._apmClient.sendToChannel === 'function'
|
|
78
|
+
) {
|
|
79
|
+
this._agent._apmClient.sendToChannel(channel, 'metricset', metric);
|
|
80
|
+
} else {
|
|
81
|
+
this._agent._apmClient.sendMetricSet(metric);
|
|
82
|
+
}
|
|
71
83
|
}
|
|
72
84
|
}
|
|
73
85
|
});
|