Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ddtrace/tracer: add startup logging #685

Merged
merged 32 commits into from
Jul 8, 2020
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
882b4a3
ddtrace/tracer: add startup logging
knusbaum Jun 24, 2020
fc02797
Add license headers.
knusbaum Jun 24, 2020
ca94122
fixes
knusbaum Jun 24, 2020
23a8016
updates
knusbaum Jun 25, 2020
a26c9b3
fix formatting.
knusbaum Jun 25, 2020
f21b035
add agent connectivity check on startup.
knusbaum Jun 25, 2020
f63217b
fix date format
knusbaum Jun 26, 2020
3ec3eda
address some PR comments
knusbaum Jun 29, 2020
b41bb7d
move startup -> log
knusbaum Jun 29, 2020
dd1dba4
don't return startupInfo, add some tests.
knusbaum Jun 30, 2020
7b656aa
more fixes
knusbaum Jun 30, 2020
f874a74
fixing tests, other stuff
knusbaum Jun 30, 2020
c61723d
better agent reachability, tests.
knusbaum Jun 30, 2020
10a1c10
fix
knusbaum Jul 1, 2020
59aac28
fixes
knusbaum Jul 2, 2020
21285c3
add comments, cleanup
knusbaum Jul 2, 2020
195b773
cleanup
knusbaum Jul 2, 2020
0486384
report agent reachability failure
knusbaum Jul 2, 2020
cd6cca5
fixes
knusbaum Jul 2, 2020
7bb0950
clean up documentation.
knusbaum Jul 2, 2020
45a729c
minor fix
knusbaum Jul 2, 2020
4e5a9da
Apply suggestions from code review
knusbaum Jul 6, 2020
3742702
address issues in PR
knusbaum Jul 6, 2020
b447177
fix logging
knusbaum Jul 6, 2020
e517c32
fix logs
knusbaum Jul 6, 2020
3513dfd
fix flaky test
knusbaum Jul 7, 2020
d1eec03
revert go.mod, go.sum
knusbaum Jul 7, 2020
7b6c9f0
Apply suggestions from code review
knusbaum Jul 7, 2020
90eff06
add newlines, add test
knusbaum Jul 7, 2020
fd3f203
Merge branch 'knusbaum/startup-logging' of github.com:DataDog/dd-trac…
knusbaum Jul 7, 2020
c4b2edb
last log change
knusbaum Jul 7, 2020
3387f14
ddtrace/tracer: remove appliedSamplingRules and add table tests
gbbr Jul 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions ddtrace/tracer/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-2020 Datadog, Inc.

package tracer

import (
"encoding/json"
"fmt"
"math"
"net/http"
"runtime"
"time"

"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
"gopkg.in/DataDog/dd-trace-go.v1/internal/version"
)

const (
unknown = "unknown"
)

// startupInfo contains various information about the status of the tracer on startup.
type startupInfo struct {
Date string `json:"date"` // ISO 8601 date and time of start
OSName string `json:"os_name"` // Windows, Darwin, Debian, etc.
OSVersion string `json:"os_version"` // Version of the OS
Version string `json:"version"` // Tracer version
Lang string `json:"lang"` // "Go"
LangVersion string `json:"lang_version"` // Go version, e.g. go1.13
Env string `json:"env"` // Tracer env
Service string `json:"service"` // Tracer Service
AgentURL string `json:"agent_url"` // The address of the agent
AgentError string `json:"agent_error"` // Any error that occurred trying to connect to agent
Debug bool `json:"debug"` // Whether debug mode is enabled
AnalyticsEnabled bool `json:"analytics_enabled"` // True if there is a global analytics rate set
SampleRate string `json:"sample_rate"` // The default sampling rate for the rules sampler
SamplingRules []SamplingRule `json:"sampling_rules"` // Rules used by the rules sampler
SamplingRulesError string `json:"sampling_rules_error"` // Any errors that occurred while parsing sampling rules
Tags map[string]string `json:"tags"` // Global tags
RuntimeMetricsEnabled bool `json:"runtime_metrics_enabled"` // Whether or not runtime metrics are enabled
HealthMetricsEnabled bool `json:"health_metrics_enabled"` // Whether or not health metrics are enabled
ApplicationVersion string `json:"dd_version"` // Version of the user's application
Architecture string `json:"architecture"` // Architecture of host machine
GlobalService string `json:"global_service"` // Global service string. If not-nil should be same as Service. (#614)
}

// checkEndpoint tries to connect to endpoint, which should be the full URL
// returned by transport.endpoint. If the endpoint is not reachable,
knusbaum marked this conversation as resolved.
Show resolved Hide resolved
// checkEndpoint returns an error explaining why.
func checkEndpoint(endpoint string) error {
req, err := http.NewRequest("POST", endpoint, nil)
if err != nil {
return fmt.Errorf("cannot create http request: %v", err)
}
_, err = defaultClient.Do(req)
if err != nil {
return err
}
return nil
}

// logStartup generates a startupInfo for a tracer and writes it to the log in
// JSON format.
func logStartup(t *tracer) {
tags := make(map[string]string)
for k, v := range t.globalTags {
tags[k] = fmt.Sprintf("%v", v)
}

info := startupInfo{
Date: time.Now().Format(time.RFC3339),
OSName: osName(),
OSVersion: osVersion(),
Version: version.Tag,
Lang: "Go",
LangVersion: runtime.Version(),
Env: t.config.env,
Service: t.config.serviceName,
AgentURL: t.transport.endpoint(),
Debug: t.config.debug,
AnalyticsEnabled: !math.IsNaN(globalconfig.AnalyticsRate()),
SampleRate: fmt.Sprintf("%f", t.rulesSampling.globalRate),
gbbr marked this conversation as resolved.
Show resolved Hide resolved
SamplingRules: t.rulesSampling.rules,
Tags: tags,
RuntimeMetricsEnabled: t.config.runtimeMetrics,
HealthMetricsEnabled: t.config.runtimeMetrics,
ApplicationVersion: t.config.version,
Architecture: runtime.GOARCH,
GlobalService: globalconfig.ServiceName(),
}
if _, err := samplingRulesFromEnv(); err != nil {
info.SamplingRulesError = fmt.Sprintf("%s", err)
}
if err := checkEndpoint(t.transport.endpoint()); err != nil {
info.AgentError = fmt.Sprintf("%s", err)
log.Warn("DIAGNOSTICS Unable to reach agent: %s", err)
}
bs, err := json.Marshal(info)
if err != nil {
log.Warn("DIAGNOSTICS Failed to serialize json for startup log (%v) %#v\n", err, info)
return
}
log.Info("CONFIGURATION %s\n", string(bs))
gbbr marked this conversation as resolved.
Show resolved Hide resolved
}
87 changes: 87 additions & 0 deletions ddtrace/tracer/log_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-2020 Datadog, Inc.

package tracer

import (
"math"
"net/http"
"os"
"testing"

"gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"

"github.com/stretchr/testify/assert"
)

func TestStartupLog(t *testing.T) {
t.Run("basic", func(t *testing.T) {
assert := assert.New(t)
tp := new(testLogger)
tracer, _, _, stop := startTestTracer(t, WithLogger(tp))
defer stop()

tp.Reset()
logStartup(tracer)
assert.Len(tp.Lines(), 2)
assert.Regexp(`Datadog Tracer v[0-9]+\.[0-9]+\.[0-9]+ INFO: CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sampling_rules":\[\],"sampling_rules_error":"","tags":{},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"dd_version":"","architecture":"[^"]*","global_service":""}`, tp.Lines()[1])
})

t.Run("configured", func(t *testing.T) {
assert := assert.New(t)
tp := new(testLogger)
os.Setenv("DD_TRACE_SAMPLE_RATE", "0.123")
defer os.Unsetenv("DD_TRACE_SAMPLE_RATE")
tracer, _, _, stop := startTestTracer(t,
WithLogger(tp),
WithService("configured.service"),
WithAgentAddr("test.host:1234"),
WithEnv("configuredEnv"),
WithGlobalTag("tag", "value"),
WithGlobalTag("tag2", math.NaN()),
WithRuntimeMetrics(),
WithAnalyticsRate(1.0),
WithServiceVersion("2.3.4"),
WithSamplingRules([]SamplingRule{ServiceRule("mysql", 0.75)}),
WithDebugMode(true),
)
defer globalconfig.SetAnalyticsRate(math.NaN())
defer globalconfig.SetServiceName("")
defer stop()

tp.Reset()
logStartup(tracer)
assert.Len(tp.Lines(), 2)
assert.Regexp(`Datadog Tracer v[0-9]+\.[0-9]+\.[0-9]+ INFO: CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"configuredEnv","service":"configured.service","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":true,"analytics_enabled":true,"sample_rate":"0\.123000","sampling_rules":\[{"service":"mysql","name":"","sample_rate":0\.75}\],"sampling_rules_error":"","tags":{"tag":"value","tag2":"NaN"},"runtime_metrics_enabled":true,"health_metrics_enabled":true,"dd_version":"2.3.4","architecture":"[^"]*","global_service":"configured.service"}`, tp.Lines()[1])
})

t.Run("errors", func(t *testing.T) {
assert := assert.New(t)
tp := new(testLogger)
os.Setenv("DD_TRACE_SAMPLING_RULES", `[{"service": "some.service", "sample_rate": 0.234}, {"service": "other.service"}]`)
defer os.Unsetenv("DD_TRACE_SAMPLING_RULES")
tracer, _, _, stop := startTestTracer(t, WithLogger(tp))
defer stop()
tracer.transport = newHTTPTransport("localhost:9", http.DefaultClient)

tp.Reset()
logStartup(tracer)
assert.Len(tp.Lines(), 2)
assert.Regexp(`Datadog Tracer v[0-9]+\.[0-9]+\.[0-9]+ INFO: CONFIGURATION {"date":"[^"]*","os_name":"[^"]*","os_version":"[^"]*","version":"[^"]*","lang":"Go","lang_version":"[^"]*","env":"","service":"tracer\.test","agent_url":"http://localhost:9/v0.4/traces","agent_error":"Post .*","debug":false,"analytics_enabled":false,"sample_rate":"NaN","sampling_rules":\[{"service":"some.service","name":"","sample_rate":0\.234}\],"sampling_rules_error":"at index 1: rate not provided","tags":{},"runtime_metrics_enabled":false,"health_metrics_enabled":false,"dd_version":"","architecture":"[^"]*","global_service":""}`, tp.Lines()[1])
})
}

func TestLogSamplingRules(t *testing.T) {
t.Run("partial", func(t *testing.T) {
assert := assert.New(t)
tp := new(testLogger)
os.Setenv("DD_TRACE_SAMPLING_RULES", `[{"service": "some.service", "sample_rate": 0.234}, {"service": "other.service"}, {"service": "last.service", "sample_rate": 0.56}, {"odd": "pairs"}, {"sample_rate": 9.10}]`)
defer os.Unsetenv("DD_TRACE_SAMPLING_RULES")
_, _, _, stop := startTestTracer(t, WithLogger(tp))
defer stop()

assert.Regexp(`Datadog Tracer v[0-9]+\.[0-9]+\.[0-9]+ WARN: DIAGNOSTICS Error\(s\) parsing DD_TRACE_SAMPLING_RULES: at index 1: rate not provided, at index 3: rate not provided$`, tp.Lines()[0])
})
}
16 changes: 16 additions & 0 deletions ddtrace/tracer/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"

Expand All @@ -29,6 +30,10 @@ type config struct {
// debug, when true, writes details to logs.
debug bool

// logStartup, when true, causes various startup info to be written
// when the tracer starts.
logStartup bool
gbbr marked this conversation as resolved.
Show resolved Hide resolved

// serviceName specifies the name of this application.
serviceName string

Expand Down Expand Up @@ -135,6 +140,7 @@ func newConfig(opts ...StartOption) *config {
}
}
}
c.logStartup = boolEnv("DD_TRACE_STARTUP_LOGS", true)
for _, fn := range opts {
fn(c)
}
Expand Down Expand Up @@ -484,3 +490,13 @@ func StackFrames(n, skip uint) FinishOption {
cfg.SkipStackFrames = skip
}
}

// boolEnv returns the parsed boolean value of an environment variable, or
// def if it fails to parse.
func boolEnv(key string, def bool) bool {
knusbaum marked this conversation as resolved.
Show resolved Hide resolved
v, err := strconv.ParseBool(os.Getenv(key))
if err != nil {
return def
}
return v
}
24 changes: 24 additions & 0 deletions ddtrace/tracer/osinfo_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-2020 Datadog, Inc.

package tracer

import (
"os/exec"
"runtime"
"strings"
)

func osName() string {
return runtime.GOOS
}

func osVersion() string {
out, err := exec.Command("sw_vers", "-productVersion").Output()
if err != nil {
return unknown
}
return strings.Trim(string(out), "\n")
}
20 changes: 20 additions & 0 deletions ddtrace/tracer/osinfo_default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-2020 Datadog, Inc.

// +build !windows,!linux,!darwin

package tracer

import (
"runtime"
)

func osName() string {
return runtime.GOOS
}

func osVersion() string {
return unknownVersion
}
52 changes: 52 additions & 0 deletions ddtrace/tracer/osinfo_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-2020 Datadog, Inc.

package tracer

import (
"bufio"
"os"
"strings"
)

func osName() string {
f, err := os.Open("/etc/os-release")
if err != nil {
return "Linux (Unknown Distribution)"
}
defer f.Close()
s := bufio.NewScanner(f)
name := "Linux (Unknown Distribution)"
for s.Scan() {
parts := strings.SplitN(s.Text(), "=", 2)
switch parts[0] {
case "Name":
name = strings.Trim(parts[1], "\"")
}
}
return name
}

func osVersion() string {
f, err := os.Open("/etc/os-release")
if err != nil {
return unknown
}
defer f.Close()
s := bufio.NewScanner(f)
version := unknown
for s.Scan() {
parts := strings.SplitN(s.Text(), "=", 2)
switch parts[0] {
case "VERSION":
version = strings.Trim(parts[1], "\"")
case "VERSION_ID":
if version == "" {
version = strings.Trim(parts[1], "\"")
}
}
}
return version
}
54 changes: 54 additions & 0 deletions ddtrace/tracer/osinfo_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-2020 Datadog, Inc.

package tracer

import (
"fmt"
"runtime"
"strings"

"golang.org/x/sys/windows/registry"
)

func osName() string {
return runtime.GOOS
}

func osVersion() string {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
if err != nil {
return unknown
}
defer k.Close()

var version strings.Builder

maj, _, err := k.GetIntegerValue("CurrentMajorVersionNumber")
knusbaum marked this conversation as resolved.
Show resolved Hide resolved
if err == nil {
version.WriteString(fmt.Sprintf("%d", maj))
min, _, err := k.GetIntegerValue("CurrentMinorVersionNumber")
if err == nil {
version.WriteString(fmt.Sprintf(".%d", min))
}
} else {
version.WriteString(unknown)
}

ed, _, err := k.GetStringValue("EditionID")
if err == nil {
version.WriteString(" " + ed)
} else {
version.WriteString(" Unknown Edition")
}

build, _, err := k.GetStringValue("CurrentBuild")
if err == nil {
version.WriteString(" Build " + build)
} else {
version.WriteString(" Unknown Build")
}
return version.String()
}
Loading