forked from microsoft/ApplicationInsights-Go
/
telemetry.go
652 lines (537 loc) · 17.5 KB
/
telemetry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
package appinsights
import (
"fmt"
"math"
"net/url"
"strconv"
"time"
"github.com/Microsoft/ApplicationInsights-Go/appinsights/contracts"
)
// Common interface implemented by telemetry data contracts
type TelemetryData interface {
EnvelopeName(string) string
BaseType() string
Sanitize() []string
}
// Common interface implemented by telemetry items that can be passed to
// TelemetryClient.Track
type Telemetry interface {
// Gets the time when this item was measured
Time() time.Time
// Sets the timestamp to the specified time.
SetTime(time.Time)
// Gets context data containing extra, optional tags. Overrides
// values found on client TelemetryContext.
ContextTags() map[string]string
// Gets the data contract as it will be submitted to the data
// collector.
TelemetryData() TelemetryData
// Gets custom properties to submit with the telemetry item.
GetProperties() map[string]string
// Gets custom measurements to submit with the telemetry item.
GetMeasurements() map[string]float64
}
// BaseTelemetry is the common base struct for telemetry items.
type BaseTelemetry struct {
// The time this when this item was measured
Timestamp time.Time
// Custom properties
Properties map[string]string
// Telemetry Context containing extra, optional tags.
Tags contracts.ContextTags
}
// BaseTelemetryMeasurements provides the Measurements field for telemetry
// items that support it.
type BaseTelemetryMeasurements struct {
// Custom measurements
Measurements map[string]float64
}
// BaseTelemetryNoMeasurements provides no Measurements field for telemetry
// items that omit it.
type BaseTelemetryNoMeasurements struct {
}
// Time returns the timestamp when this was measured.
func (item *BaseTelemetry) Time() time.Time {
return item.Timestamp
}
// SetTime sets the timestamp to the specified time.
func (item *BaseTelemetry) SetTime(t time.Time) {
item.Timestamp = t
}
// Gets context data containing extra, optional tags. Overrides values
// found on client TelemetryContext.
func (item *BaseTelemetry) ContextTags() map[string]string {
return item.Tags
}
// Gets custom properties to submit with the telemetry item.
func (item *BaseTelemetry) GetProperties() map[string]string {
return item.Properties
}
// Gets custom measurements to submit with the telemetry item.
func (item *BaseTelemetryMeasurements) GetMeasurements() map[string]float64 {
return item.Measurements
}
// GetMeasurements returns nil for telemetry items that do not support measurements.
func (item *BaseTelemetryNoMeasurements) GetMeasurements() map[string]float64 {
return nil
}
// Trace telemetry items represent printf-like trace statements that can be
// text searched.
type TraceTelemetry struct {
BaseTelemetry
BaseTelemetryNoMeasurements
// Trace message
Message string
// Severity level
SeverityLevel contracts.SeverityLevel
}
// Creates a trace telemetry item with the specified message and severity
// level.
func NewTraceTelemetry(message string, severityLevel contracts.SeverityLevel) *TraceTelemetry {
return &TraceTelemetry{
Message: message,
SeverityLevel: severityLevel,
BaseTelemetry: BaseTelemetry{
Timestamp: currentClock.Now(),
Tags: make(contracts.ContextTags),
Properties: make(map[string]string),
},
}
}
func (trace *TraceTelemetry) TelemetryData() TelemetryData {
data := contracts.NewMessageData()
data.Message = trace.Message
data.Properties = trace.Properties
data.SeverityLevel = trace.SeverityLevel
return data
}
// Event telemetry items represent structured event records.
type EventTelemetry struct {
BaseTelemetry
BaseTelemetryMeasurements
// Event name
Name string
}
// Creates an event telemetry item with the specified name.
func NewEventTelemetry(name string) *EventTelemetry {
return &EventTelemetry{
Name: name,
BaseTelemetry: BaseTelemetry{
Timestamp: currentClock.Now(),
Tags: make(contracts.ContextTags),
Properties: make(map[string]string),
},
BaseTelemetryMeasurements: BaseTelemetryMeasurements{
Measurements: make(map[string]float64),
},
}
}
func (event *EventTelemetry) TelemetryData() TelemetryData {
data := contracts.NewEventData()
data.Name = event.Name
data.Properties = event.Properties
data.Measurements = event.Measurements
return data
}
// Metric telemetry items each represent a single data point.
type MetricTelemetry struct {
BaseTelemetry
BaseTelemetryNoMeasurements
// Metric name
Name string
// Sampled value
Value float64
}
// Creates a metric telemetry sample with the specified name and value.
func NewMetricTelemetry(name string, value float64) *MetricTelemetry {
return &MetricTelemetry{
Name: name,
Value: value,
BaseTelemetry: BaseTelemetry{
Timestamp: currentClock.Now(),
Tags: make(contracts.ContextTags),
Properties: make(map[string]string),
},
}
}
func (metric *MetricTelemetry) TelemetryData() TelemetryData {
dataPoint := contracts.NewDataPoint()
dataPoint.Name = metric.Name
dataPoint.Value = metric.Value
dataPoint.Count = 1
dataPoint.Kind = contracts.Measurement
data := contracts.NewMetricData()
data.Metrics = []*contracts.DataPoint{dataPoint}
data.Properties = metric.Properties
return data
}
// Aggregated metric telemetry items represent an aggregation of data points
// over time. These values can be calculated by the caller or with the AddData
// function.
type AggregateMetricTelemetry struct {
BaseTelemetry
BaseTelemetryNoMeasurements
// Metric name
Name string
// Sum of individual measurements
Value float64
// Minimum value of the aggregated metric
Min float64
// Maximum value of the aggregated metric
Max float64
// Count of measurements in the sample
Count int
// Standard deviation of the aggregated metric
StdDev float64
// Variance of the aggregated metric. As an invariant,
// either this or the StdDev should be zero at any given time.
// If both are non-zero then StdDev takes precedence.
Variance float64
}
// Creates a new aggregated metric telemetry item with the specified name.
// Values should be set on the object returned before submission.
func NewAggregateMetricTelemetry(name string) *AggregateMetricTelemetry {
return &AggregateMetricTelemetry{
Name: name,
Count: 0,
BaseTelemetry: BaseTelemetry{
Timestamp: currentClock.Now(),
Tags: make(contracts.ContextTags),
Properties: make(map[string]string),
},
}
}
// Adds data points to the aggregate totals included in this telemetry item.
// This can be used for all the data at once or incrementally. Calculates
// Min, Max, Sum, Count, and StdDev (by way of Variance).
func (agg *AggregateMetricTelemetry) AddData(values []float64) {
if agg.StdDev != 0.0 {
// If StdDev is non-zero, then square it to produce
// the variance, which is better for incremental calculations,
// and then zero it out.
agg.Variance = agg.StdDev * agg.StdDev
agg.StdDev = 0.0
}
vsum := agg.addData(values, agg.Variance*float64(agg.Count))
if agg.Count > 0 {
agg.Variance = vsum / float64(agg.Count)
}
}
// Adds sampled data points to the aggregate totals included in this telemetry item.
// This can be used for all the data at once or incrementally. Differs from AddData
// in how it calculates standard deviation, and should not be used interchangeably
// with AddData.
func (agg *AggregateMetricTelemetry) AddSampledData(values []float64) {
if agg.StdDev != 0.0 {
// If StdDev is non-zero, then square it to produce
// the variance, which is better for incremental calculations,
// and then zero it out.
agg.Variance = agg.StdDev * agg.StdDev
agg.StdDev = 0.0
}
vsum := agg.addData(values, agg.Variance*float64(agg.Count-1))
if agg.Count > 1 {
// Sampled values should divide by n-1
agg.Variance = vsum / float64(agg.Count-1)
}
}
func (agg *AggregateMetricTelemetry) addData(values []float64, vsum float64) float64 {
if len(values) == 0 {
return vsum
}
// Running tally of the mean is important for incremental variance computation.
var mean float64
if agg.Count == 0 {
agg.Min = values[0]
agg.Max = values[0]
} else {
mean = agg.Value / float64(agg.Count)
}
for _, x := range values {
// Update Min, Max, Count, and Value
agg.Count++
agg.Value += x
if x < agg.Min {
agg.Min = x
}
if x > agg.Max {
agg.Max = x
}
// Welford's algorithm to compute variance. The divide occurs in the caller.
newMean := agg.Value / float64(agg.Count)
vsum += (x - mean) * (x - newMean)
mean = newMean
}
return vsum
}
func (agg *AggregateMetricTelemetry) TelemetryData() TelemetryData {
dataPoint := contracts.NewDataPoint()
dataPoint.Name = agg.Name
dataPoint.Value = agg.Value
dataPoint.Kind = contracts.Aggregation
dataPoint.Min = agg.Min
dataPoint.Max = agg.Max
dataPoint.Count = agg.Count
if agg.StdDev != 0.0 {
dataPoint.StdDev = agg.StdDev
} else if agg.Variance > 0.0 {
dataPoint.StdDev = math.Sqrt(agg.Variance)
}
data := contracts.NewMetricData()
data.Metrics = []*contracts.DataPoint{dataPoint}
data.Properties = agg.Properties
return data
}
// Request telemetry items represents completion of an external request to the
// application and contains a summary of that request execution and results.
type RequestTelemetry struct {
BaseTelemetry
BaseTelemetryMeasurements
// Identifier of a request call instance. Used for correlation between request
// and other telemetry items.
Id string
// Request name. For HTTP requests it represents the HTTP method and URL path template.
Name string
// URL of the request with all query string parameters.
Url string
// Duration to serve the request.
Duration time.Duration
// Results of a request execution. HTTP status code for HTTP requests.
ResponseCode string
// Indication of successful or unsuccessful call.
Success bool
// Source of the request. Examplese are the instrumentation key of the caller
// or the ip address of the caller.
Source string
}
// Creates a new request telemetry item for HTTP requests. The success value will be
// computed from responseCode, and the timestamp will be set to the current time minus
// the duration.
func NewRequestTelemetry(method, uri string, duration time.Duration, responseCode string) *RequestTelemetry {
success := true
code, err := strconv.Atoi(responseCode)
if err == nil {
success = code < 400 || code == 401
}
nameUri := uri
// Sanitize URL for the request name
if parsedUrl, err := url.Parse(uri); err == nil {
// Remove the query
parsedUrl.RawQuery = ""
parsedUrl.ForceQuery = false
// Remove the fragment
parsedUrl.Fragment = ""
// Remove the user info, if any.
parsedUrl.User = nil
// Write back to name
nameUri = parsedUrl.String()
}
return &RequestTelemetry{
Name: fmt.Sprintf("%s %s", method, nameUri),
Url: uri,
Id: newUUID().String(),
Duration: duration,
ResponseCode: responseCode,
Success: success,
BaseTelemetry: BaseTelemetry{
Timestamp: currentClock.Now().Add(-duration),
Tags: make(contracts.ContextTags),
Properties: make(map[string]string),
},
BaseTelemetryMeasurements: BaseTelemetryMeasurements{
Measurements: make(map[string]float64),
},
}
}
// Sets the timestamp and duration of this telemetry item based on the provided
// start and end times.
func (request *RequestTelemetry) MarkTime(startTime, endTime time.Time) {
request.Timestamp = startTime
request.Duration = endTime.Sub(startTime)
}
func (request *RequestTelemetry) TelemetryData() TelemetryData {
data := contracts.NewRequestData()
data.Name = request.Name
data.Duration = formatDuration(request.Duration)
data.ResponseCode = request.ResponseCode
data.Success = request.Success
data.Url = request.Url
data.Source = request.Source
if request.Id == "" {
data.Id = newUUID().String()
} else {
data.Id = request.Id
}
data.Properties = request.Properties
data.Measurements = request.Measurements
return data
}
// Remote dependency telemetry items represent interactions of the monitored
// component with a remote component/service like SQL or an HTTP endpoint.
type RemoteDependencyTelemetry struct {
BaseTelemetry
BaseTelemetryMeasurements
// Name of the command that initiated this dependency call. Low cardinality
// value. Examples are stored procedure name and URL path template.
Name string
// Identifier of a dependency call instance. Used for correlation with the
// request telemetry item corresponding to this dependency call.
Id string
// Result code of a dependency call. Examples are SQL error code and HTTP
// status code.
ResultCode string
// Duration of the remote call.
Duration time.Duration
// Indication of successful or unsuccessful call.
Success bool
// Command initiated by this dependency call. Examples are SQL statement and
// HTTP URL's with all the query parameters.
Data string
// Dependency type name. Very low cardinality. Examples are SQL, Azure table,
// and HTTP.
Type string
// Target site of a dependency call. Examples are server name, host address.
Target string
}
// Builds a new Remote Dependency telemetry item, with the specified name,
// dependency type, target site, and success status.
func NewRemoteDependencyTelemetry(name, dependencyType, target string, success bool) *RemoteDependencyTelemetry {
return &RemoteDependencyTelemetry{
Name: name,
Type: dependencyType,
Target: target,
Success: success,
BaseTelemetry: BaseTelemetry{
Timestamp: currentClock.Now(),
Tags: make(contracts.ContextTags),
Properties: make(map[string]string),
},
BaseTelemetryMeasurements: BaseTelemetryMeasurements{
Measurements: make(map[string]float64),
},
}
}
// Sets the timestamp and duration of this telemetry item based on the provided
// start and end times.
func (telem *RemoteDependencyTelemetry) MarkTime(startTime, endTime time.Time) {
telem.Timestamp = startTime
telem.Duration = endTime.Sub(startTime)
}
func (telem *RemoteDependencyTelemetry) TelemetryData() TelemetryData {
data := contracts.NewRemoteDependencyData()
data.Name = telem.Name
data.Id = telem.Id
data.ResultCode = telem.ResultCode
data.Duration = formatDuration(telem.Duration)
data.Success = telem.Success
data.Data = telem.Data
data.Target = telem.Target
data.Properties = telem.Properties
data.Measurements = telem.Measurements
data.Type = telem.Type
return data
}
// Avaibility telemetry items represent the result of executing an availability
// test.
type AvailabilityTelemetry struct {
BaseTelemetry
BaseTelemetryMeasurements
// Identifier of a test run. Used to correlate steps of test run and
// telemetry generated by the service.
Id string
// Name of the test that this result represents.
Name string
// Duration of the test run.
Duration time.Duration
// Success flag.
Success bool
// Name of the location where the test was run.
RunLocation string
// Diagnostic message for the result.
Message string
}
// Creates a new availability telemetry item with the specified test name,
// duration and success code.
func NewAvailabilityTelemetry(name string, duration time.Duration, success bool) *AvailabilityTelemetry {
return &AvailabilityTelemetry{
Name: name,
Duration: duration,
Success: success,
BaseTelemetry: BaseTelemetry{
Timestamp: currentClock.Now(),
Tags: make(contracts.ContextTags),
Properties: make(map[string]string),
},
BaseTelemetryMeasurements: BaseTelemetryMeasurements{
Measurements: make(map[string]float64),
},
}
}
// Sets the timestamp and duration of this telemetry item based on the provided
// start and end times.
func (telem *AvailabilityTelemetry) MarkTime(startTime, endTime time.Time) {
telem.Timestamp = startTime
telem.Duration = endTime.Sub(startTime)
}
func (telem *AvailabilityTelemetry) TelemetryData() TelemetryData {
data := contracts.NewAvailabilityData()
data.Name = telem.Name
data.Duration = formatDuration(telem.Duration)
data.Success = telem.Success
data.RunLocation = telem.RunLocation
data.Message = telem.Message
data.Properties = telem.Properties
data.Id = telem.Id
data.Measurements = telem.Measurements
return data
}
// Page view telemetry items represent generic actions on a page like a button
// click.
type PageViewTelemetry struct {
BaseTelemetry
BaseTelemetryMeasurements
// Request URL with all query string parameters
Url string
// Request duration.
Duration time.Duration
// Event name.
Name string
}
// Creates a new page view telemetry item with the specified name and url.
func NewPageViewTelemetry(name, url string) *PageViewTelemetry {
return &PageViewTelemetry{
Name: name,
Url: url,
BaseTelemetry: BaseTelemetry{
Timestamp: currentClock.Now(),
Tags: make(contracts.ContextTags),
Properties: make(map[string]string),
},
BaseTelemetryMeasurements: BaseTelemetryMeasurements{
Measurements: make(map[string]float64),
},
}
}
// Sets the timestamp and duration of this telemetry item based on the provided
// start and end times.
func (telem *PageViewTelemetry) MarkTime(startTime, endTime time.Time) {
telem.Timestamp = startTime
telem.Duration = endTime.Sub(startTime)
}
func (telem *PageViewTelemetry) TelemetryData() TelemetryData {
data := contracts.NewPageViewData()
data.Url = telem.Url
data.Duration = formatDuration(telem.Duration)
data.Name = telem.Name
data.Properties = telem.Properties
data.Measurements = telem.Measurements
return data
}
func formatDuration(d time.Duration) string {
ticks := int64(d/(time.Nanosecond*100)) % 10000000
seconds := int64(d/time.Second) % 60
minutes := int64(d/time.Minute) % 60
hours := int64(d/time.Hour) % 24
days := int64(d / (time.Hour * 24))
return fmt.Sprintf("%d.%02d:%02d:%02d.%07d", days, hours, minutes, seconds, ticks)
}