2017-08-03 00:58:26 +00:00
package zipkin
import (
"context"
"fmt"
"net"
"net/http"
"strconv"
"sync"
"github.com/gorilla/mux"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
2017-08-22 00:24:54 +00:00
"github.com/influxdata/telegraf/plugins/inputs/zipkin/trace"
2017-08-03 00:58:26 +00:00
)
const (
// DefaultPort is the default port zipkin listens on, which zipkin implementations
// expect.
DefaultPort = 9411
// DefaultRoute is the default route zipkin uses, and zipkin implementations
// expect.
DefaultRoute = "/api/v1/spans"
// DefaultShutdownTimeout is the max amount of time telegraf will wait
// for the plugin to shutdown
DefaultShutdownTimeout = 5
)
2018-04-18 23:14:06 +00:00
var (
// DefaultNetwork is the network to listen on; use only in tests.
DefaultNetwork = "tcp"
)
2017-08-03 00:58:26 +00:00
// Recorder represents a type which can record zipkin trace data as well as
// any accompanying errors, and process that data.
type Recorder interface {
2017-08-22 00:24:54 +00:00
Record ( trace . Trace ) error
2017-08-03 00:58:26 +00:00
Error ( error )
}
// Handler represents a type which can register itself with a router for
// http routing, and a Recorder for trace data collection.
type Handler interface {
Register ( router * mux . Router , recorder Recorder ) error
}
const sampleConfig = `
# path = "/api/v1/spans" # URL path for span data
# port = 9411 # Port on which Telegraf listens
`
// Zipkin is a telegraf configuration structure for the zipkin input plugin,
// but it also contains fields for the management of a separate, concurrent
// zipkin http server
type Zipkin struct {
ServiceAddress string
Port int
Path string
2019-09-23 22:39:50 +00:00
Log telegraf . Logger
2017-08-03 00:58:26 +00:00
address string
handler Handler
server * http . Server
waitGroup * sync . WaitGroup
}
// Description is a necessary method implementation from telegraf.ServiceInput
func ( z Zipkin ) Description ( ) string {
return "This plugin implements the Zipkin http server to gather trace and timing data needed to troubleshoot latency problems in microservice architectures."
}
// SampleConfig is a necessary method implementation from telegraf.ServiceInput
func ( z Zipkin ) SampleConfig ( ) string {
return sampleConfig
}
// Gather is empty for the zipkin plugin; all gathering is done through
// the separate goroutine launched in (*Zipkin).Start()
func ( z * Zipkin ) Gather ( acc telegraf . Accumulator ) error { return nil }
// Start launches a separate goroutine for collecting zipkin client http requests,
// passing in a telegraf.Accumulator such that data can be collected.
func ( z * Zipkin ) Start ( acc telegraf . Accumulator ) error {
z . handler = NewSpanHandler ( z . Path )
var wg sync . WaitGroup
z . waitGroup = & wg
router := mux . NewRouter ( )
converter := NewLineProtocolConverter ( acc )
2017-08-22 00:24:54 +00:00
if err := z . handler . Register ( router , converter ) ; err != nil {
return err
}
2017-08-03 00:58:26 +00:00
z . server = & http . Server {
Handler : router ,
}
addr := ":" + strconv . Itoa ( z . Port )
2018-04-18 23:14:06 +00:00
ln , err := net . Listen ( DefaultNetwork , addr )
2017-08-03 00:58:26 +00:00
if err != nil {
return err
}
z . address = ln . Addr ( ) . String ( )
2019-09-23 22:39:50 +00:00
z . Log . Infof ( "Started the zipkin listener on %s" , z . address )
2017-08-03 00:58:26 +00:00
go func ( ) {
wg . Add ( 1 )
defer wg . Done ( )
z . Listen ( ln , acc )
} ( )
return nil
}
// Stop shuts the internal http server down with via context.Context
func ( z * Zipkin ) Stop ( ) {
ctx , cancel := context . WithTimeout ( context . Background ( ) , DefaultShutdownTimeout )
defer z . waitGroup . Wait ( )
defer cancel ( )
z . server . Shutdown ( ctx )
}
// Listen creates an http server on the zipkin instance it is called with, and
// serves http until it is stopped by Zipkin's (*Zipkin).Stop() method.
func ( z * Zipkin ) Listen ( ln net . Listener , acc telegraf . Accumulator ) {
if err := z . server . Serve ( ln ) ; err != nil {
// Because of the clean shutdown in `(*Zipkin).Stop()`
// We're expecting a server closed error at some point
// So we don't want to display it as an error.
// This interferes with telegraf's internal data collection,
// by making it appear as if a serious error occurred.
if err != http . ErrServerClosed {
acc . AddError ( fmt . Errorf ( "E! Error listening: %v" , err ) )
}
}
}
func init ( ) {
inputs . Add ( "zipkin" , func ( ) telegraf . Input {
return & Zipkin {
Path : DefaultRoute ,
Port : DefaultPort ,
}
} )
}