1
0
Fork 0

Create init method on provider interface

This commit is contained in:
SALLEYRON Julien 2018-07-11 09:08:03 +02:00 committed by Traefiker Bot
parent b2a57ca1f3
commit 027093a5a5
52 changed files with 2760 additions and 131 deletions

View file

@ -0,0 +1,122 @@
package appinsights
type Domain interface {
}
type domain struct {
Ver int `json:"ver"`
Properties map[string]string `json:"properties"`
}
type data struct {
BaseType string `json:"baseType"`
BaseData Domain `json:"baseData"`
}
type envelope struct {
Name string `json:"name"`
Time string `json:"time"`
IKey string `json:"iKey"`
Tags map[string]string `json:"tags"`
Data *data `json:"data"`
}
type DataPointType int
const (
Measurement DataPointType = iota
Aggregation
)
type DataPoint struct {
Name string `json:"name"`
Kind DataPointType `json:"kind"`
Value float32 `json:"value"`
Count int `json:"count"`
min float32 `json:"min"`
max float32 `json:"max"`
stdDev float32 `json:"stdDev"`
}
type metricData struct {
domain
Metrics []*DataPoint `json:"metrics"`
}
type eventData struct {
domain
Name string `json:"name"`
Measurements map[string]float32 `json:"measurements"`
}
type SeverityLevel int
const (
Verbose SeverityLevel = iota
Information
Warning
Error
Critical
)
type messageData struct {
domain
Message string `json:"message"`
SeverityLevel SeverityLevel `json:"severityLevel"`
}
type requestData struct {
domain
Id string `json:"id"`
Name string `json:"name"`
StartTime string `json:"startTime"` // yyyy-mm-ddThh:mm:ss.fffffff-hh:mm
Duration string `json:"duration"` // d:hh:mm:ss.fffffff
ResponseCode string `json:"responseCode"`
Success bool `json:"success"`
HttpMethod string `json:"httpMethod"`
Url string `json:"url"`
Measurements map[string]float32 `json:"measurements"`
}
type ContextTagKeys string
const (
ApplicationVersion ContextTagKeys = "ai.application.ver"
ApplicationBuild = "ai.application.build"
CloudRole = "ai.cloud.role"
CloudRoleInstance = "ai.cloud.roleInstance"
DeviceId = "ai.device.id"
DeviceIp = "ai.device.ip"
DeviceLanguage = "ai.device.language"
DeviceLocale = "ai.device.locale"
DeviceModel = "ai.device.model"
DeviceNetwork = "ai.device.network"
DeviceOEMName = "ai.device.oemName"
DeviceOS = "ai.device.os"
DeviceOSVersion = "ai.device.osVersion"
DeviceRoleInstance = "ai.device.roleInstance"
DeviceRoleName = "ai.device.roleName"
DeviceScreenResolution = "ai.device.screenResolution"
DeviceType = "ai.device.type"
DeviceMachineName = "ai.device.machineName"
LocationIp = "ai.location.ip"
OperationCorrelationVector = "ai.operation.correlationVector"
OperationId = "ai.operation.id"
OperationName = "ai.operation.name"
OperationParentId = "ai.operation.parentId"
OperationRootId = "ai.operation.rootId"
OperationSyntheticSource = "ai.operation.syntheticSource"
OperationIsSynthetic = "ai.operation.isSynthetic"
SessionId = "ai.session.id"
SessionIsFirst = "ai.session.isFirst"
SessionIsNew = "ai.session.isNew"
UserAccountAcquisitionDate = "ai.user.accountAcquisitionDate"
UserAccountId = "ai.user.accountId"
UserAgent = "ai.user.userAgent"
UserAuthUserId = "ai.user.authUserId"
UserId = "ai.user.id"
UserStoreRegion = "ai.user.storeRegion"
SampleRate = "ai.sample.sampleRate"
InternalSdkVersion = "ai.internal.sdkVersion"
InternalAgentVersion = "ai.internal.agentVersion"
)

View file

@ -0,0 +1,132 @@
package appinsights
import "time"
type TelemetryClient interface {
Context() TelemetryContext
InstrumentationKey() string
Channel() TelemetryChannel
IsEnabled() bool
SetIsEnabled(bool)
Track(Telemetry)
TrackEvent(string)
TrackEventTelemetry(*EventTelemetry)
TrackMetric(string, float32)
TrackMetricTelemetry(*MetricTelemetry)
TrackTrace(string)
TrackTraceTelemetry(*TraceTelemetry)
TrackRequest(string, string, string, time.Time, time.Duration, string, bool)
TrackRequestTelemetry(*RequestTelemetry)
}
type telemetryClient struct {
TelemetryConfiguration *TelemetryConfiguration
channel TelemetryChannel
context TelemetryContext
isEnabled bool
}
func NewTelemetryClient(iKey string) TelemetryClient {
return NewTelemetryClientFromConfig(NewTelemetryConfiguration(iKey))
}
func NewTelemetryClientFromConfig(config *TelemetryConfiguration) TelemetryClient {
channel := NewInMemoryChannel(config)
context := NewClientTelemetryContext()
return &telemetryClient{
TelemetryConfiguration: config,
channel: channel,
context: context,
isEnabled: true,
}
}
func (tc *telemetryClient) Context() TelemetryContext {
return tc.context
}
func (tc *telemetryClient) Channel() TelemetryChannel {
return tc.channel
}
func (tc *telemetryClient) InstrumentationKey() string {
return tc.TelemetryConfiguration.InstrumentationKey
}
func (tc *telemetryClient) IsEnabled() bool {
return tc.isEnabled
}
func (tc *telemetryClient) SetIsEnabled(isEnabled bool) {
tc.isEnabled = isEnabled
}
func (tc *telemetryClient) Track(item Telemetry) {
if tc.isEnabled {
iKey := tc.context.InstrumentationKey()
if len(iKey) == 0 {
iKey = tc.TelemetryConfiguration.InstrumentationKey
}
itemContext := item.Context().(*telemetryContext)
itemContext.iKey = iKey
clientContext := tc.context.(*telemetryContext)
for tagkey, tagval := range clientContext.tags {
if itemContext.tags[tagkey] == "" {
itemContext.tags[tagkey] = tagval
}
}
tc.channel.Send(item)
}
}
func (tc *telemetryClient) TrackEvent(name string) {
item := NewEventTelemetry(name)
tc.TrackEventTelemetry(item)
}
func (tc *telemetryClient) TrackEventTelemetry(event *EventTelemetry) {
var item Telemetry
item = event
tc.Track(item)
}
func (tc *telemetryClient) TrackMetric(name string, value float32) {
item := NewMetricTelemetry(name, value)
tc.TrackMetricTelemetry(item)
}
func (tc *telemetryClient) TrackMetricTelemetry(metric *MetricTelemetry) {
var item Telemetry
item = metric
tc.Track(item)
}
func (tc *telemetryClient) TrackTrace(message string) {
item := NewTraceTelemetry(message, Information)
tc.TrackTraceTelemetry(item)
}
func (tc *telemetryClient) TrackTraceTelemetry(trace *TraceTelemetry) {
var item Telemetry
item = trace
tc.Track(item)
}
func (tc *telemetryClient) TrackRequest(name, method, url string, timestamp time.Time, duration time.Duration, responseCode string, success bool) {
item := NewRequestTelemetry(name, method, url, timestamp, duration, responseCode, success)
tc.TrackRequestTelemetry(item)
}
func (tc *telemetryClient) TrackRequestTelemetry(request *RequestTelemetry) {
var item Telemetry
item = request
tc.Track(item)
}

View file

@ -0,0 +1,11 @@
package appinsights
// We need to mock out the clock for tests; we'll use this to do it.
import "code.cloudfoundry.org/clock"
var currentClock clock.Clock
func init() {
currentClock = clock.NewClock()
}

View file

@ -0,0 +1,45 @@
package appinsights
import (
"encoding/base64"
"math/rand"
"sync/atomic"
"time"
"unsafe"
)
type concurrentRandom struct {
channel chan string
random *rand.Rand
}
var randomGenerator *concurrentRandom
func newConcurrentRandom() *concurrentRandom {
source := rand.NewSource(time.Now().UnixNano())
return &concurrentRandom{
channel: make(chan string, 4),
random: rand.New(source),
}
}
func (generator *concurrentRandom) run() {
buf := make([]byte, 8)
for {
generator.random.Read(buf)
generator.channel <- base64.StdEncoding.EncodeToString(buf)
}
}
func randomId() string {
if randomGenerator == nil {
r := newConcurrentRandom()
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&randomGenerator)), unsafe.Pointer(nil), unsafe.Pointer(r)) {
go r.run()
} else {
close(r.channel)
}
}
return <-randomGenerator.channel
}

View file

@ -0,0 +1,19 @@
package appinsights
import "time"
type TelemetryConfiguration struct {
InstrumentationKey string
EndpointUrl string
MaxBatchSize int
MaxBatchInterval time.Duration
}
func NewTelemetryConfiguration(instrumentationKey string) *TelemetryConfiguration {
return &TelemetryConfiguration{
InstrumentationKey: instrumentationKey,
EndpointUrl: "https://dc.services.visualstudio.com/v2/track",
MaxBatchSize: 1024,
MaxBatchInterval: time.Duration(10) * time.Second,
}
}

View file

@ -0,0 +1,228 @@
package appinsights
import (
"fmt"
"time"
)
type Telemetry interface {
Timestamp() time.Time
Context() TelemetryContext
baseTypeName() string
baseData() Domain
SetProperty(string, string)
}
type BaseTelemetry struct {
timestamp time.Time
context TelemetryContext
}
type TraceTelemetry struct {
BaseTelemetry
data *messageData
}
func NewTraceTelemetry(message string, severityLevel SeverityLevel) *TraceTelemetry {
now := time.Now()
data := &messageData{
Message: message,
SeverityLevel: severityLevel,
}
data.Ver = 2
item := &TraceTelemetry{
data: data,
}
item.timestamp = now
item.context = NewItemTelemetryContext()
return item
}
func (item *TraceTelemetry) Timestamp() time.Time {
return item.timestamp
}
func (item *TraceTelemetry) Context() TelemetryContext {
return item.context
}
func (item *TraceTelemetry) baseTypeName() string {
return "Message"
}
func (item *TraceTelemetry) baseData() Domain {
return item.data
}
func (item *TraceTelemetry) SetProperty(key, value string) {
if item.data.Properties == nil {
item.data.Properties = make(map[string]string)
}
item.data.Properties[key] = value
}
type EventTelemetry struct {
BaseTelemetry
data *eventData
}
func NewEventTelemetry(name string) *EventTelemetry {
now := time.Now()
data := &eventData{
Name: name,
}
data.Ver = 2
item := &EventTelemetry{
data: data,
}
item.timestamp = now
item.context = NewItemTelemetryContext()
return item
}
func (item *EventTelemetry) Timestamp() time.Time {
return item.timestamp
}
func (item *EventTelemetry) Context() TelemetryContext {
return item.context
}
func (item *EventTelemetry) baseTypeName() string {
return "Event"
}
func (item *EventTelemetry) baseData() Domain {
return item.data
}
func (item *EventTelemetry) SetProperty(key, value string) {
if item.data.Properties == nil {
item.data.Properties = make(map[string]string)
}
item.data.Properties[key] = value
}
type MetricTelemetry struct {
BaseTelemetry
data *metricData
}
func NewMetricTelemetry(name string, value float32) *MetricTelemetry {
now := time.Now()
metric := &DataPoint{
Name: name,
Value: value,
Count: 1,
}
data := &metricData{
Metrics: make([]*DataPoint, 1),
}
data.Ver = 2
data.Metrics[0] = metric
item := &MetricTelemetry{
data: data,
}
item.timestamp = now
item.context = NewItemTelemetryContext()
return item
}
func (item *MetricTelemetry) Timestamp() time.Time {
return item.timestamp
}
func (item *MetricTelemetry) Context() TelemetryContext {
return item.context
}
func (item *MetricTelemetry) baseTypeName() string {
return "Metric"
}
func (item *MetricTelemetry) baseData() Domain {
return item.data
}
func (item *MetricTelemetry) SetProperty(key, value string) {
if item.data.Properties == nil {
item.data.Properties = make(map[string]string)
}
item.data.Properties[key] = value
}
type RequestTelemetry struct {
BaseTelemetry
data *requestData
}
func NewRequestTelemetry(name, httpMethod, url string, timestamp time.Time, duration time.Duration, responseCode string, success bool) *RequestTelemetry {
now := time.Now()
data := &requestData{
Name: name,
StartTime: timestamp.Format(time.RFC3339Nano),
Duration: formatDuration(duration),
ResponseCode: responseCode,
Success: success,
HttpMethod: httpMethod,
Url: url,
Id: randomId(),
}
data.Ver = 2
item := &RequestTelemetry{
data: data,
}
item.timestamp = now
item.context = NewItemTelemetryContext()
return item
}
func (item *RequestTelemetry) Timestamp() time.Time {
return item.timestamp
}
func (item *RequestTelemetry) Context() TelemetryContext {
return item.context
}
func (item *RequestTelemetry) baseTypeName() string {
return "Request"
}
func (item *RequestTelemetry) baseData() Domain {
return item.data
}
func (item *RequestTelemetry) SetProperty(key, value string) {
if item.data.Properties == nil {
item.data.Properties = make(map[string]string)
}
item.data.Properties[key] = value
}
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)
}

View file

@ -0,0 +1,64 @@
package appinsights
import "fmt"
type DiagnosticsMessageWriter interface {
Write(string)
appendListener(*diagnosticsMessageListener)
}
type diagnosticsMessageWriter struct {
listeners []chan string
}
type DiagnosticsMessageProcessor func(string)
type DiagnosticsMessageListener interface {
ProcessMessages(DiagnosticsMessageProcessor)
}
type diagnosticsMessageListener struct {
channel chan string
}
var diagnosticsWriter *diagnosticsMessageWriter = &diagnosticsMessageWriter{
listeners: make([]chan string, 0),
}
func NewDiagnosticsMessageListener() DiagnosticsMessageListener {
listener := &diagnosticsMessageListener{
channel: make(chan string),
}
diagnosticsWriter.appendListener(listener)
return listener
}
func (writer *diagnosticsMessageWriter) appendListener(listener *diagnosticsMessageListener) {
writer.listeners = append(writer.listeners, listener.channel)
}
func (writer *diagnosticsMessageWriter) Write(message string) {
for _, c := range writer.listeners {
c <- message
}
}
func (writer *diagnosticsMessageWriter) Printf(message string, args ...interface{}) {
// Don't bother with Sprintf if nobody is listening
if writer.hasListeners() {
writer.Write(fmt.Sprintf(message, args...))
}
}
func (writer *diagnosticsMessageWriter) hasListeners() bool {
return len(writer.listeners) > 0
}
func (listener *diagnosticsMessageListener) ProcessMessages(process DiagnosticsMessageProcessor) {
for {
message := <-listener.channel
process(message)
}
}

View file

@ -0,0 +1,408 @@
package appinsights
import (
"sync"
"time"
"code.cloudfoundry.org/clock"
)
var (
submit_retries = []time.Duration{time.Duration(10 * time.Second), time.Duration(30 * time.Second), time.Duration(60 * time.Second)}
)
type TelemetryBufferItems []Telemetry
type InMemoryChannel struct {
endpointAddress string
isDeveloperMode bool
collectChan chan Telemetry
controlChan chan *inMemoryChannelControl
batchSize int
batchInterval time.Duration
waitgroup sync.WaitGroup
throttle *throttleManager
transmitter transmitter
}
type inMemoryChannelControl struct {
// If true, flush the buffer.
flush bool
// If true, stop listening on the channel. (Flush is required if any events are to be sent)
stop bool
// If stopping and flushing, this specifies whether to retry submissions on error.
retry bool
// If retrying, what is the max time to wait before finishing up?
timeout time.Duration
// If specified, a message will be sent on this channel when all pending telemetry items have been submitted
callback chan struct{}
}
func NewInMemoryChannel(config *TelemetryConfiguration) *InMemoryChannel {
channel := &InMemoryChannel{
endpointAddress: config.EndpointUrl,
collectChan: make(chan Telemetry),
controlChan: make(chan *inMemoryChannelControl),
batchSize: config.MaxBatchSize,
batchInterval: config.MaxBatchInterval,
throttle: newThrottleManager(),
transmitter: newTransmitter(config.EndpointUrl),
}
go channel.acceptLoop()
return channel
}
func (channel *InMemoryChannel) EndpointAddress() string {
return channel.endpointAddress
}
func (channel *InMemoryChannel) Send(item Telemetry) {
if item != nil && channel.collectChan != nil {
channel.collectChan <- item
}
}
func (channel *InMemoryChannel) Flush() {
if channel.controlChan != nil {
channel.controlChan <- &inMemoryChannelControl{
flush: true,
}
}
}
func (channel *InMemoryChannel) Stop() {
if channel.controlChan != nil {
channel.controlChan <- &inMemoryChannelControl{
stop: true,
}
}
}
func (channel *InMemoryChannel) IsThrottled() bool {
return channel.throttle != nil && channel.throttle.IsThrottled()
}
func (channel *InMemoryChannel) Close(timeout ...time.Duration) <-chan struct{} {
if channel.controlChan != nil {
callback := make(chan struct{})
ctl := &inMemoryChannelControl{
stop: true,
flush: true,
retry: false,
callback: callback,
}
if len(timeout) > 0 {
ctl.retry = true
ctl.timeout = timeout[0]
}
channel.controlChan <- ctl
return callback
} else {
return nil
}
}
func (channel *InMemoryChannel) acceptLoop() {
channelState := newInMemoryChannelState(channel)
for !channelState.stopping {
channelState.start()
}
channelState.stop()
}
// Data shared between parts of a channel
type inMemoryChannelState struct {
channel *InMemoryChannel
stopping bool
buffer TelemetryBufferItems
retry bool
retryTimeout time.Duration
callback chan struct{}
timer clock.Timer
}
func newInMemoryChannelState(channel *InMemoryChannel) *inMemoryChannelState {
return &inMemoryChannelState{
channel: channel,
buffer: make(TelemetryBufferItems, 0, 16),
stopping: false,
timer: currentClock.NewTimer(channel.batchInterval),
}
}
// Part of channel accept loop: Initialize buffer and accept first message, handle controls.
func (state *inMemoryChannelState) start() bool {
if len(state.buffer) > 16 {
// Start out with the size of the previous buffer
state.buffer = make(TelemetryBufferItems, 0, cap(state.buffer))
} else if len(state.buffer) > 0 {
// Start out with at least 16 slots
state.buffer = make(TelemetryBufferItems, 0, 16)
}
// Wait for an event
select {
case event := <-state.channel.collectChan:
if event == nil {
// Channel closed? Not intercepted by Send()?
panic("Received nil event")
}
state.buffer = append(state.buffer, event)
case ctl := <-state.channel.controlChan:
// The buffer is empty, so there would be no point in flushing
state.channel.signalWhenDone(ctl.callback)
if ctl.stop {
state.stopping = true
return false
}
}
if len(state.buffer) == 0 {
return true
}
return state.waitToSend()
}
// Part of channel accept loop: Wait for buffer to fill, timeout to expire, or flush
func (state *inMemoryChannelState) waitToSend() bool {
// Things that are used by the sender if we receive a control message
state.retryTimeout = 0
state.retry = true
state.callback = nil
// Delay until timeout passes or buffer fills up
state.timer.Reset(state.channel.batchInterval)
for {
select {
case event := <-state.channel.collectChan:
if event == nil {
// Channel closed? Not intercepted by Send()?
panic("Received nil event")
}
state.buffer = append(state.buffer, event)
if len(state.buffer) >= state.channel.batchSize {
return state.send()
}
case ctl := <-state.channel.controlChan:
if ctl.stop {
state.stopping = true
state.retry = ctl.retry
if !ctl.flush {
// No flush? Just exit.
state.channel.signalWhenDone(ctl.callback)
return false
}
}
if ctl.flush {
state.retryTimeout = ctl.timeout
state.callback = ctl.callback
return state.send()
}
case _ = <-state.timer.C():
// Timeout expired
return state.send()
}
}
}
// Part of channel accept loop: Check and wait on throttle, submit pending telemetry
func (state *inMemoryChannelState) send() bool {
// Hold up transmission if we're being throttled
if !state.stopping && state.channel.throttle.IsThrottled() {
if !state.waitThrottle() {
// Stopped
return false
}
}
// Send
if len(state.buffer) > 0 {
state.channel.waitgroup.Add(1)
// If we have a callback, wait on the waitgroup now that it's
// incremented.
state.channel.signalWhenDone(state.callback)
go func(buffer TelemetryBufferItems, retry bool, retryTimeout time.Duration) {
defer state.channel.waitgroup.Done()
state.channel.transmitRetry(buffer, retry, retryTimeout)
}(state.buffer, state.retry, state.retryTimeout)
} else if state.callback != nil {
state.channel.signalWhenDone(state.callback)
}
return true
}
// Part of channel accept loop: Wait for throttle to expire while dropping messages
func (state *inMemoryChannelState) waitThrottle() bool {
// Channel is currently throttled. Once the buffer fills, messages will
// be lost... If we're exiting, then we'll just try to submit anyway. That
// request may be throttled and transmitRetry will perform the backoff correctly.
diagnosticsWriter.Write("Channel is throttled, events may be dropped.")
throttleDone := state.channel.throttle.NotifyWhenReady()
dropped := 0
defer diagnosticsWriter.Printf("Channel dropped %d events while throttled", dropped)
for {
select {
case <-throttleDone:
close(throttleDone)
return true
case event := <-state.channel.collectChan:
// If there's still room in the buffer, then go ahead and add it.
if len(state.buffer) < state.channel.batchSize {
state.buffer = append(state.buffer, event)
} else {
if dropped == 0 {
diagnosticsWriter.Write("Buffer is full, dropping further events.")
}
dropped++
}
case ctl := <-state.channel.controlChan:
if ctl.stop {
state.stopping = true
state.retry = ctl.retry
if !ctl.flush {
state.channel.signalWhenDone(ctl.callback)
return false
} else {
// Make an exception when stopping
return true
}
}
// Cannot flush
// TODO: Figure out what to do about callback?
if ctl.flush {
state.channel.signalWhenDone(ctl.callback)
}
}
}
}
// Part of channel accept loop: Clean up and close telemetry channel
func (state *inMemoryChannelState) stop() {
close(state.channel.collectChan)
close(state.channel.controlChan)
state.channel.collectChan = nil
state.channel.controlChan = nil
// Throttle can't close until transmitters are done using it.
state.channel.waitgroup.Wait()
state.channel.throttle.Stop()
state.channel.throttle = nil
}
func (channel *InMemoryChannel) transmitRetry(items TelemetryBufferItems, retry bool, retryTimeout time.Duration) {
payload := items.serialize()
retryTimeRemaining := retryTimeout
for _, wait := range submit_retries {
result, err := channel.transmitter.Transmit(payload, items)
if err == nil && result != nil && result.IsSuccess() {
return
}
if !retry {
diagnosticsWriter.Write("Refusing to retry telemetry submission (retry==false)")
return
}
// Check for success, determine if we need to retry anything
if result != nil {
if result.CanRetry() {
// Filter down to failed items
payload, items = result.GetRetryItems(payload, items)
if len(payload) == 0 || len(items) == 0 {
return
}
} else {
diagnosticsWriter.Write("Cannot retry telemetry submission")
return
}
// Check for throttling
if result.IsThrottled() {
if result.retryAfter != nil {
diagnosticsWriter.Printf("Channel is throttled until %s", *result.retryAfter)
channel.throttle.RetryAfter(*result.retryAfter)
} else {
// TODO: Pick a time
}
}
}
if retryTimeout > 0 {
// We're on a time schedule here. Make sure we don't try longer
// than we have been allowed.
if retryTimeRemaining < wait {
// One more chance left -- we'll wait the max time we can
// and then retry on the way out.
currentClock.Sleep(retryTimeRemaining)
break
} else {
// Still have time left to go through the rest of the regular
// retry schedule
retryTimeRemaining -= wait
}
}
diagnosticsWriter.Printf("Waiting %s to retry submission", wait)
currentClock.Sleep(wait)
// Wait if the channel is throttled and we're not on a schedule
if channel.IsThrottled() && retryTimeout == 0 {
diagnosticsWriter.Printf("Channel is throttled; extending wait time.")
ch := channel.throttle.NotifyWhenReady()
result := <-ch
close(ch)
if !result {
return
}
}
}
// One final try
_, err := channel.transmitter.Transmit(payload, items)
if err != nil {
diagnosticsWriter.Write("Gave up transmitting payload; exhausted retries")
}
}
func (channel *InMemoryChannel) signalWhenDone(callback chan struct{}) {
if callback != nil {
go func() {
channel.waitgroup.Wait()
close(callback)
}()
}
}

View file

@ -0,0 +1,45 @@
package appinsights
import (
"bytes"
"encoding/json"
"fmt"
"time"
)
func (items TelemetryBufferItems) serialize() []byte {
var result bytes.Buffer
encoder := json.NewEncoder(&result)
for _, item := range items {
end := result.Len()
if err := encoder.Encode(prepare(item)); err != nil {
diagnosticsWriter.Write(fmt.Sprintf("Telemetry item failed to serialize: %s", err.Error()))
result.Truncate(end)
}
}
return result.Bytes()
}
func prepare(item Telemetry) *envelope {
data := &data{
BaseType: item.baseTypeName() + "Data",
BaseData: item.baseData(),
}
context := item.Context()
envelope := &envelope{
Name: "Microsoft.ApplicationInsights." + item.baseTypeName(),
Time: item.Timestamp().Format(time.RFC3339),
IKey: context.InstrumentationKey(),
Data: data,
}
if tcontext, ok := context.(*telemetryContext); ok {
envelope.Tags = tcontext.tags
}
return envelope
}

View file

@ -0,0 +1,8 @@
// Package appinsights provides an interface to submit telemetry to Application Insights.
// See more at https://azure.microsoft.com/en-us/services/application-insights/
package appinsights
const (
sdkName = "go"
Version = "0.3.1-pre"
)

View file

@ -0,0 +1,47 @@
package appinsights
import "time"
// Implementations of TelemetryChannel are responsible for queueing and
// periodically submitting telemetry items.
type TelemetryChannel interface {
// The address of the endpoint to which telemetry is sent
EndpointAddress() string
// Queues a single telemetry item
Send(Telemetry)
// Forces the current queue to be sent
Flush()
// Tears down the submission goroutines, closes internal channels.
// Any telemetry waiting to be sent is discarded. Further calls to
// Send() have undefined behavior. This is a more abrupt version of
// Close().
Stop()
// Returns true if this channel has been throttled by the data
// collector.
IsThrottled() bool
// Flushes and tears down the submission goroutine and closes
// internal channels. Returns a channel that is closed when all
// pending telemetry items have been submitted and it is safe to
// shut down without losing telemetry.
//
// If retryTimeout is specified and non-zero, then failed
// submissions will be retried until one succeeds or the timeout
// expires, whichever occurs first. A retryTimeout of zero
// indicates that failed submissions will be retried as usual. An
// omitted retryTimeout indicates that submissions should not be
// retried if they fail.
//
// Note that the returned channel may not be closed before
// retryTimeout even if it is specified. This is because
// retryTimeout only applies to the latest telemetry buffer. This
// may be typical for applications that submit a large amount of
// telemetry or are prone to being throttled. When exiting, you
// should select on the result channel and your own timer to avoid
// long delays.
Close(retryTimeout ...time.Duration) <-chan struct{}
}

View file

@ -0,0 +1,400 @@
package appinsights
import (
"os"
"runtime"
"strconv"
)
type TelemetryContext interface {
InstrumentationKey() string
loadDeviceContext()
Component() ComponentContext
Device() DeviceContext
Cloud() CloudContext
Session() SessionContext
User() UserContext
Operation() OperationContext
Location() LocationContext
}
type telemetryContext struct {
iKey string
tags map[string]string
}
type ComponentContext interface {
GetVersion() string
SetVersion(string)
}
type DeviceContext interface {
GetType() string
SetType(string)
GetId() string
SetId(string)
GetOperatingSystem() string
SetOperatingSystem(string)
GetOemName() string
SetOemName(string)
GetModel() string
SetModel(string)
GetNetworkType() string
SetNetworkType(string)
GetScreenResolution() string
SetScreenResolution(string)
GetLanguage() string
SetLanguage(string)
}
type CloudContext interface {
GetRoleName() string
SetRoleName(string)
GetRoleInstance() string
SetRoleInstance(string)
}
type SessionContext interface {
GetId() string
SetId(string)
GetIsFirst() bool
SetIsFirst(bool)
}
type UserContext interface {
GetId() string
SetId(string)
GetAccountId() string
SetAccountId(string)
GetUserAgent() string
SetUserAgent(string)
GetAuthenticatedUserId() string
SetAuthenticatedUserId(string)
}
type OperationContext interface {
GetId() string
SetId(string)
GetParentId() string
SetParentId(string)
GetCorrelationVector() string
SetCorrelationVector(string)
GetName() string
SetName(string)
GetSyntheticSource() string
SetSyntheticSource(string)
}
type LocationContext interface {
GetIp() string
SetIp(string)
}
func NewItemTelemetryContext() TelemetryContext {
context := &telemetryContext{
tags: make(map[string]string),
}
return context
}
func NewClientTelemetryContext() TelemetryContext {
context := &telemetryContext{
tags: make(map[string]string),
}
context.loadDeviceContext()
context.loadInternalContext()
return context
}
func (context *telemetryContext) InstrumentationKey() string {
return context.iKey
}
func (context *telemetryContext) loadDeviceContext() {
hostname, err := os.Hostname()
if err == nil {
context.tags[DeviceId] = hostname
context.tags[DeviceMachineName] = hostname
context.tags[DeviceRoleInstance] = hostname
}
context.tags[DeviceOS] = runtime.GOOS
}
func (context *telemetryContext) loadInternalContext() {
context.tags[InternalSdkVersion] = sdkName + ":" + Version
}
func (context *telemetryContext) Component() ComponentContext {
return &componentContext{context: context}
}
func (context *telemetryContext) Device() DeviceContext {
return &deviceContext{context: context}
}
func (context *telemetryContext) Cloud() CloudContext {
return &cloudContext{context: context}
}
func (context *telemetryContext) Session() SessionContext {
return &sessionContext{context: context}
}
func (context *telemetryContext) User() UserContext {
return &userContext{context: context}
}
func (context *telemetryContext) Operation() OperationContext {
return &operationContext{context: context}
}
func (context *telemetryContext) Location() LocationContext {
return &locationContext{context: context}
}
func (context *telemetryContext) getTagString(key ContextTagKeys) string {
if val, ok := context.tags[string(key)]; ok {
return val
}
return ""
}
func (context *telemetryContext) setTagString(key ContextTagKeys, value string) {
if value != "" {
context.tags[string(key)] = value
} else {
delete(context.tags, string(key))
}
}
func (context *telemetryContext) getTagBool(key ContextTagKeys) bool {
if val, ok := context.tags[string(key)]; ok {
if b, err := strconv.ParseBool(val); err != nil {
return b
}
}
return false
}
func (context *telemetryContext) setTagBool(key ContextTagKeys, value bool) {
if value {
context.tags[string(key)] = "true"
} else {
delete(context.tags, string(key))
}
}
type componentContext struct {
context *telemetryContext
}
type deviceContext struct {
context *telemetryContext
}
type cloudContext struct {
context *telemetryContext
}
type sessionContext struct {
context *telemetryContext
}
type userContext struct {
context *telemetryContext
}
type operationContext struct {
context *telemetryContext
}
type locationContext struct {
context *telemetryContext
}
func (context *componentContext) GetVersion() string {
return context.context.getTagString(ApplicationVersion)
}
func (context *componentContext) SetVersion(value string) {
context.context.setTagString(ApplicationVersion, value)
}
func (context *deviceContext) GetType() string {
return context.context.getTagString(DeviceType)
}
func (context *deviceContext) SetType(value string) {
context.context.setTagString(DeviceType, value)
}
func (context *deviceContext) GetId() string {
return context.context.getTagString(DeviceId)
}
func (context *deviceContext) SetId(value string) {
context.context.setTagString(DeviceId, value)
}
func (context *deviceContext) GetOperatingSystem() string {
return context.context.getTagString(DeviceOSVersion)
}
func (context *deviceContext) SetOperatingSystem(value string) {
context.context.setTagString(DeviceOSVersion, value)
}
func (context *deviceContext) GetOemName() string {
return context.context.getTagString(DeviceOEMName)
}
func (context *deviceContext) SetOemName(value string) {
context.context.setTagString(DeviceOEMName, value)
}
func (context *deviceContext) GetModel() string {
return context.context.getTagString(DeviceModel)
}
func (context *deviceContext) SetModel(value string) {
context.context.setTagString(DeviceModel, value)
}
func (context *deviceContext) GetNetworkType() string {
return context.context.getTagString(DeviceNetwork)
}
func (context *deviceContext) SetNetworkType(value string) {
context.context.setTagString(DeviceNetwork, value)
}
func (context *deviceContext) GetScreenResolution() string {
return context.context.getTagString(DeviceScreenResolution)
}
func (context *deviceContext) SetScreenResolution(value string) {
context.context.setTagString(DeviceScreenResolution, value)
}
func (context *deviceContext) GetLanguage() string {
return context.context.getTagString(DeviceLanguage)
}
func (context *deviceContext) SetLanguage(value string) {
context.context.setTagString(DeviceLanguage, value)
}
func (context *cloudContext) GetRoleName() string {
return context.context.getTagString(CloudRole)
}
func (context *cloudContext) SetRoleName(value string) {
context.context.setTagString(CloudRole, value)
}
func (context *cloudContext) GetRoleInstance() string {
return context.context.getTagString(CloudRoleInstance)
}
func (context *cloudContext) SetRoleInstance(value string) {
context.context.setTagString(CloudRoleInstance, value)
}
func (context *sessionContext) GetId() string {
return context.context.getTagString(SessionId)
}
func (context *sessionContext) SetId(value string) {
context.context.setTagString(SessionId, value)
}
func (context *sessionContext) GetIsFirst() bool {
return context.context.getTagBool(SessionIsFirst)
}
func (context *sessionContext) SetIsFirst(value bool) {
context.context.setTagBool(SessionIsFirst, value)
}
func (context *userContext) GetId() string {
return context.context.getTagString(UserId)
}
func (context *userContext) SetId(value string) {
context.context.setTagString(UserId, value)
}
func (context *userContext) GetAccountId() string {
return context.context.getTagString(UserAccountId)
}
func (context *userContext) SetAccountId(value string) {
context.context.setTagString(UserAccountId, value)
}
func (context *userContext) GetUserAgent() string {
return context.context.getTagString(UserAgent)
}
func (context *userContext) SetUserAgent(value string) {
context.context.setTagString(UserAgent, value)
}
func (context *userContext) GetAuthenticatedUserId() string {
return context.context.getTagString(UserAuthUserId)
}
func (context *userContext) SetAuthenticatedUserId(value string) {
context.context.setTagString(UserAuthUserId, value)
}
func (context *operationContext) GetId() string {
return context.context.getTagString(OperationId)
}
func (context *operationContext) SetId(value string) {
context.context.setTagString(OperationId, value)
}
func (context *operationContext) GetParentId() string {
return context.context.getTagString(OperationParentId)
}
func (context *operationContext) SetParentId(value string) {
context.context.setTagString(OperationParentId, value)
}
func (context *operationContext) GetCorrelationVector() string {
return context.context.getTagString(OperationCorrelationVector)
}
func (context *operationContext) SetCorrelationVector(value string) {
context.context.setTagString(OperationCorrelationVector, value)
}
func (context *operationContext) GetName() string {
return context.context.getTagString(OperationName)
}
func (context *operationContext) SetName(value string) {
context.context.setTagString(OperationName, value)
}
func (context *operationContext) GetSyntheticSource() string {
return context.context.getTagString(OperationSyntheticSource)
}
func (context *operationContext) SetSyntheticSource(value string) {
context.context.setTagString(OperationSyntheticSource, value)
}
func (context *locationContext) GetIp() string {
return context.context.getTagString(LocationIp)
}
func (context *locationContext) SetIp(value string) {
context.context.setTagString(LocationIp, value)
}

View file

@ -0,0 +1,144 @@
package appinsights
import (
"time"
)
type throttleManager struct {
msgs chan *throttleMessage
}
type throttleMessage struct {
query bool
wait bool
throttle bool
stop bool
timestamp time.Time
result chan bool
}
func newThrottleManager() *throttleManager {
result := &throttleManager{
msgs: make(chan *throttleMessage),
}
go result.run()
return result
}
func (throttle *throttleManager) RetryAfter(t time.Time) {
throttle.msgs <- &throttleMessage{
throttle: true,
timestamp: t,
}
}
func (throttle *throttleManager) IsThrottled() bool {
ch := make(chan bool)
throttle.msgs <- &throttleMessage{
query: true,
result: ch,
}
result := <-ch
close(ch)
return result
}
func (throttle *throttleManager) NotifyWhenReady() chan bool {
result := make(chan bool, 1)
throttle.msgs <- &throttleMessage{
wait: true,
result: result,
}
return result
}
func (throttle *throttleManager) Stop() {
result := make(chan bool)
throttle.msgs <- &throttleMessage{
stop: true,
result: result,
}
<-result
close(result)
}
func (throttle *throttleManager) run() {
for {
throttledUntil, ok := throttle.waitForThrottle()
if !ok {
break
}
if !throttle.waitForReady(throttledUntil) {
break
}
}
close(throttle.msgs)
}
func (throttle *throttleManager) waitForThrottle() (time.Time, bool) {
for {
msg := <-throttle.msgs
if msg.query {
msg.result <- false
} else if msg.wait {
msg.result <- true
} else if msg.stop {
return time.Time{}, false
} else if msg.throttle {
return msg.timestamp, true
}
}
}
func (throttle *throttleManager) waitForReady(throttledUntil time.Time) bool {
duration := throttledUntil.Sub(currentClock.Now())
if duration <= 0 {
return true
}
var notify []chan bool
// --- Throttled and waiting ---
t := currentClock.NewTimer(duration)
for {
select {
case <-t.C():
for _, n := range notify {
n <- true
}
return true
case msg := <-throttle.msgs:
if msg.query {
msg.result <- true
} else if msg.wait {
notify = append(notify, msg.result)
} else if msg.stop {
for _, n := range notify {
n <- false
}
msg.result <- true
return false
} else if msg.throttle {
if msg.timestamp.After(throttledUntil) {
throttledUntil = msg.timestamp
if !t.Stop() {
<-t.C()
}
t.Reset(throttledUntil.Sub(currentClock.Now()))
}
}
}
}
}

View file

@ -0,0 +1,237 @@
package appinsights
import (
"bytes"
"compress/gzip"
"encoding/json"
"io/ioutil"
"net/http"
"sort"
"time"
)
type transmitter interface {
Transmit(payload []byte, items TelemetryBufferItems) (*transmissionResult, error)
}
type httpTransmitter struct {
endpoint string
}
type transmissionResult struct {
statusCode int
retryAfter *time.Time
response *backendResponse
}
// Structures returned by data collector
type backendResponse struct {
ItemsReceived int `json:"itemsReceived"`
ItemsAccepted int `json:"itemsAccepted"`
Errors itemTransmissionResults `json:"errors"`
}
// This needs to be its own type because it implements sort.Interface
type itemTransmissionResults []*itemTransmissionResult
type itemTransmissionResult struct {
Index int `json:"index"`
StatusCode int `json:"statusCode"`
Message string `json:"message"`
}
const (
successResponse = 200
partialSuccessResponse = 206
requestTimeoutResponse = 408
tooManyRequestsResponse = 429
tooManyRequestsOverExtendedTimeResponse = 439
errorResponse = 500
serviceUnavailableResponse = 503
)
func newTransmitter(endpointAddress string) transmitter {
return &httpTransmitter{endpointAddress}
}
func (transmitter *httpTransmitter) Transmit(payload []byte, items TelemetryBufferItems) (*transmissionResult, error) {
diagnosticsWriter.Printf("----------- Transmitting %d items ---------", len(items))
startTime := time.Now()
// Compress the payload
var postBody bytes.Buffer
gzipWriter := gzip.NewWriter(&postBody)
if _, err := gzipWriter.Write(payload); err != nil {
diagnosticsWriter.Printf("Failed to compress the payload: %s", err.Error())
gzipWriter.Close()
return nil, err
}
gzipWriter.Close()
req, err := http.NewRequest("POST", transmitter.endpoint, &postBody)
if err != nil {
return nil, err
}
req.Header.Set("Content-Encoding", "gzip")
req.Header.Set("Content-Type", "application/x-json-stream")
req.Header.Set("Accept-Encoding", "gzip, deflate")
client := http.DefaultClient
resp, err := client.Do(req)
if err != nil {
diagnosticsWriter.Printf("Failed to transmit telemetry: %s", err.Error())
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
diagnosticsWriter.Printf("Failed to read response from server: %s", err.Error())
return nil, err
}
duration := time.Since(startTime)
result := &transmissionResult{statusCode: resp.StatusCode}
// Grab Retry-After header
if retryAfterValue, ok := resp.Header[http.CanonicalHeaderKey("Retry-After")]; ok && len(retryAfterValue) == 1 {
if retryAfterTime, err := time.Parse(time.RFC1123, retryAfterValue[0]); err == nil {
result.retryAfter = &retryAfterTime
}
}
// Parse body, if possible
response := &backendResponse{}
if err := json.Unmarshal(body, &response); err == nil {
result.response = response
}
// Write diagnostics
if diagnosticsWriter.hasListeners() {
diagnosticsWriter.Printf("Telemetry transmitted in %s", duration)
diagnosticsWriter.Printf("Response: %d", result.statusCode)
if result.response != nil {
diagnosticsWriter.Printf("Items accepted/received: %d/%d", result.response.ItemsAccepted, result.response.ItemsReceived)
if len(result.response.Errors) > 0 {
diagnosticsWriter.Printf("Errors:")
for _, err := range result.response.Errors {
if err.Index < len(items) {
diagnosticsWriter.Printf("#%d - %d %s", err.Index, err.StatusCode, err.Message)
diagnosticsWriter.Printf("Telemetry item:\n\t%s", err.Index, string(items[err.Index:err.Index+1].serialize()))
}
}
}
}
}
return result, nil
}
func (result *transmissionResult) IsSuccess() bool {
return result.statusCode == successResponse ||
// Partial response but all items accepted
(result.statusCode == partialSuccessResponse &&
result.response != nil &&
result.response.ItemsReceived == result.response.ItemsAccepted)
}
func (result *transmissionResult) IsFailure() bool {
return result.statusCode != successResponse && result.statusCode != partialSuccessResponse
}
func (result *transmissionResult) CanRetry() bool {
if result.IsSuccess() {
return false
}
return result.statusCode == partialSuccessResponse ||
result.retryAfter != nil ||
(result.statusCode == requestTimeoutResponse ||
result.statusCode == serviceUnavailableResponse ||
result.statusCode == errorResponse ||
result.statusCode == tooManyRequestsResponse ||
result.statusCode == tooManyRequestsOverExtendedTimeResponse)
}
func (result *transmissionResult) IsPartialSuccess() bool {
return result.statusCode == partialSuccessResponse &&
result.response != nil &&
result.response.ItemsReceived != result.response.ItemsAccepted
}
func (result *transmissionResult) IsThrottled() bool {
return result.statusCode == tooManyRequestsResponse ||
result.statusCode == tooManyRequestsOverExtendedTimeResponse ||
result.retryAfter != nil
}
func (result *itemTransmissionResult) CanRetry() bool {
return result.StatusCode == requestTimeoutResponse ||
result.StatusCode == serviceUnavailableResponse ||
result.StatusCode == errorResponse ||
result.StatusCode == tooManyRequestsResponse ||
result.StatusCode == tooManyRequestsOverExtendedTimeResponse
}
func (result *transmissionResult) GetRetryItems(payload []byte, items TelemetryBufferItems) ([]byte, TelemetryBufferItems) {
if result.statusCode == partialSuccessResponse && result.response != nil {
// Make sure errors are ordered by index
sort.Sort(result.response.Errors)
var resultPayload bytes.Buffer
resultItems := make(TelemetryBufferItems, 0)
ptr := 0
idx := 0
// Find each retryable error
for _, responseResult := range result.response.Errors {
if responseResult.CanRetry() {
// Advance ptr to start of desired line
for ; idx < responseResult.Index && ptr < len(payload); ptr++ {
if payload[ptr] == '\n' {
idx++
}
}
startPtr := ptr
// Read to end of line
for ; idx == responseResult.Index && ptr < len(payload); ptr++ {
if payload[ptr] == '\n' {
idx++
}
}
// Copy item into output buffer
resultPayload.Write(payload[startPtr:ptr])
resultItems = append(resultItems, items[responseResult.Index])
}
}
return resultPayload.Bytes(), resultItems
} else if result.CanRetry() {
return payload, items
} else {
return payload[:0], items[:0]
}
}
// sort.Interface implementation for Errors[] list
func (results itemTransmissionResults) Len() int {
return len(results)
}
func (results itemTransmissionResults) Less(i, j int) bool {
return results[i].Index < results[j].Index
}
func (results itemTransmissionResults) Swap(i, j int) {
tmp := results[i]
results[i] = results[j]
results[j] = tmp
}