Commit 4df8028b authored by Matthew Holt's avatar Matthew Holt

diagnostics: Add/remove metrics

parent 385ea533
...@@ -123,6 +123,7 @@ type Instance struct { ...@@ -123,6 +123,7 @@ type Instance struct {
StorageMu sync.RWMutex StorageMu sync.RWMutex
} }
// Instances returns the list of instances.
func Instances() []*Instance { func Instances() []*Instance {
return instances return instances
} }
...@@ -616,7 +617,7 @@ func ValidateAndExecuteDirectives(cdyfile Input, inst *Instance, justValidate bo ...@@ -616,7 +617,7 @@ func ValidateAndExecuteDirectives(cdyfile Input, inst *Instance, justValidate bo
return fmt.Errorf("error inspecting server blocks: %v", err) return fmt.Errorf("error inspecting server blocks: %v", err)
} }
diagnostics.Set("num_server_blocks", len(sblocks)) diagnostics.Set("http_num_server_blocks", len(sblocks))
return executeDirectives(inst, cdyfile.Path(), stype.Directives(), sblocks, justValidate) return executeDirectives(inst, cdyfile.Path(), stype.Directives(), sblocks, justValidate)
} }
...@@ -872,7 +873,7 @@ func Stop() error { ...@@ -872,7 +873,7 @@ func Stop() error {
// explicitly like a common local hostname. addr must only // explicitly like a common local hostname. addr must only
// be a host or a host:port combination. // be a host or a host:port combination.
func IsLoopback(addr string) bool { func IsLoopback(addr string) bool {
host, _, err := net.SplitHostPort(addr) host, _, err := net.SplitHostPort(strings.ToLower(addr))
if err != nil { if err != nil {
host = addr // happens if the addr is just a hostname host = addr // happens if the addr is just a hostname
} }
......
...@@ -51,6 +51,9 @@ type tlsHandler struct { ...@@ -51,6 +51,9 @@ type tlsHandler struct {
// Halderman, et. al. in "The Security Impact of HTTPS Interception" (NDSS '17): // Halderman, et. al. in "The Security Impact of HTTPS Interception" (NDSS '17):
// https://jhalderm.com/pub/papers/interception-ndss17.pdf // https://jhalderm.com/pub/papers/interception-ndss17.pdf
func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// TODO: one request per connection, we should report UA in connection with
// handshake (reported in caddytls package) and our MITM assessment
if h.listener == nil { if h.listener == nil {
h.next.ServeHTTP(w, r) h.next.ServeHTTP(w, r)
return return
...@@ -100,12 +103,12 @@ func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ...@@ -100,12 +103,12 @@ func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if checked { if checked {
r = r.WithContext(context.WithValue(r.Context(), MitmCtxKey, mitm)) r = r.WithContext(context.WithValue(r.Context(), MitmCtxKey, mitm))
if mitm { if mitm {
go diagnostics.AppendUnique("mitm", "likely") go diagnostics.AppendUnique("http_mitm", "likely")
} else { } else {
go diagnostics.AppendUnique("mitm", "unlikely") go diagnostics.AppendUnique("http_mitm", "unlikely")
} }
} else { } else {
go diagnostics.AppendUnique("mitm", "unknown") go diagnostics.AppendUnique("http_mitm", "unknown")
} }
if mitm && h.closeOnMITM { if mitm && h.closeOnMITM {
......
...@@ -29,6 +29,7 @@ import ( ...@@ -29,6 +29,7 @@ import (
"github.com/mholt/caddy/caddyfile" "github.com/mholt/caddy/caddyfile"
"github.com/mholt/caddy/caddyhttp/staticfiles" "github.com/mholt/caddy/caddyhttp/staticfiles"
"github.com/mholt/caddy/caddytls" "github.com/mholt/caddy/caddytls"
"github.com/mholt/caddy/diagnostics"
) )
const serverType = "http" const serverType = "http"
...@@ -205,9 +206,34 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd ...@@ -205,9 +206,34 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd
// MakeServers uses the newly-created siteConfigs to // MakeServers uses the newly-created siteConfigs to
// create and return a list of server instances. // create and return a list of server instances.
func (h *httpContext) MakeServers() ([]caddy.Server, error) { func (h *httpContext) MakeServers() ([]caddy.Server, error) {
// make sure TLS is disabled for explicitly-HTTP sites // make a rough estimate as to whether we're in a "production
// (necessary when HTTP address shares a block containing tls) // environment/system" - start by assuming that most production
// servers will set their default CA endpoint to a public,
// trusted CA (obviously not a perfect hueristic)
var looksLikeProductionCA bool
for _, publicCAEndpoint := range caddytls.KnownACMECAs {
if strings.Contains(caddytls.DefaultCAUrl, publicCAEndpoint) {
looksLikeProductionCA = true
break
}
}
var atLeastOneSiteLooksLikeProduction bool
for _, cfg := range h.siteConfigs { for _, cfg := range h.siteConfigs {
// if we aren't sure yet whether it's a "production" server,
// continue to see if all the addresses (both sites and
// listeners) are loopback
if !atLeastOneSiteLooksLikeProduction {
if !caddy.IsLoopback(cfg.Addr.Host) &&
!caddy.IsLoopback(cfg.ListenHost) &&
(caddytls.QualifiesForManagedTLS(cfg) ||
caddytls.HostQualifies(cfg.Addr.Host)) {
atLeastOneSiteLooksLikeProduction = true
}
}
// make sure TLS is disabled for explicitly-HTTP sites
// (necessary when HTTP address shares a block containing tls)
if !cfg.TLS.Enabled { if !cfg.TLS.Enabled {
continue continue
} }
...@@ -246,6 +272,18 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) { ...@@ -246,6 +272,18 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) {
servers = append(servers, s) servers = append(servers, s)
} }
// NOTE: This value is only a "good" guess. Quite often, development
// environments will use internal DNS or a local hosts file to serve
// real-looking domains in local development. We can't easily tell
// which without doing a DNS lookup, so this guess is definitely naive,
// and if we ever want a better guess, we will have to do DNS lookups.
deploymentGuess := "dev"
if looksLikeProductionCA && atLeastOneSiteLooksLikeProduction {
deploymentGuess = "production"
}
diagnostics.Set("http_deployment_guess", deploymentGuess)
diagnostics.Set("http_num_sites", len(h.siteConfigs))
return servers, nil return servers, nil
} }
......
...@@ -346,7 +346,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { ...@@ -346,7 +346,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
}() }()
go diagnostics.AppendUnique("user_agent", r.Header.Get("User-Agent")) // TODO: Somehow report UA string in conjunction with TLS handshake, if any (and just once per connection)
go diagnostics.AppendUnique("http_user_agent", r.Header.Get("User-Agent"))
go diagnostics.Increment("http_request_count")
// copy the original, unchanged URL into the context // copy the original, unchanged URL into the context
// so it can be referenced by middlewares // so it can be referenced by middlewares
......
...@@ -26,6 +26,7 @@ import ( ...@@ -26,6 +26,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/mholt/caddy/diagnostics"
"golang.org/x/crypto/ocsp" "golang.org/x/crypto/ocsp"
) )
...@@ -165,6 +166,7 @@ func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) { ...@@ -165,6 +166,7 @@ func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) {
if err != nil { if err != nil {
return cert, err return cert, err
} }
diagnostics.Increment("tls_managed_cert_count")
return cfg.cacheCertificate(cert), nil return cfg.cacheCertificate(cert), nil
} }
...@@ -179,6 +181,7 @@ func (cfg *Config) cacheUnmanagedCertificatePEMFile(certFile, keyFile string) er ...@@ -179,6 +181,7 @@ func (cfg *Config) cacheUnmanagedCertificatePEMFile(certFile, keyFile string) er
return err return err
} }
cfg.cacheCertificate(cert) cfg.cacheCertificate(cert)
diagnostics.Increment("tls_manual_cert_count")
return nil return nil
} }
...@@ -192,6 +195,7 @@ func (cfg *Config) cacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte) ...@@ -192,6 +195,7 @@ func (cfg *Config) cacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte)
return err return err
} }
cfg.cacheCertificate(cert) cfg.cacheCertificate(cert)
diagnostics.Increment("tls_manual_cert_count")
return nil return nil
} }
......
...@@ -268,7 +268,7 @@ Attempts: ...@@ -268,7 +268,7 @@ Attempts:
break break
} }
go diagnostics.Increment("acme_certificates_obtained") go diagnostics.Increment("tls_acme_certs_obtained")
return nil return nil
} }
...@@ -340,8 +340,7 @@ func (c *ACMEClient) Renew(name string) error { ...@@ -340,8 +340,7 @@ func (c *ACMEClient) Renew(name string) error {
} }
caddy.EmitEvent(caddy.CertRenewEvent, name) caddy.EmitEvent(caddy.CertRenewEvent, name)
go diagnostics.Increment("acme_certificates_obtained") go diagnostics.Increment("tls_acme_certs_renewed")
go diagnostics.Increment("acme_certificates_renewed")
return saveCertResource(c.storage, newCertMeta) return saveCertResource(c.storage, newCertMeta)
} }
...@@ -368,6 +367,8 @@ func (c *ACMEClient) Revoke(name string) error { ...@@ -368,6 +367,8 @@ func (c *ACMEClient) Revoke(name string) error {
return err return err
} }
go diagnostics.Increment("tls_acme_certs_revoked")
err = c.storage.DeleteSite(name) err = c.storage.DeleteSite(name)
if err != nil { if err != nil {
return errors.New("certificate revoked, but unable to delete certificate file: " + err.Error()) return errors.New("certificate revoked, but unable to delete certificate file: " + err.Error())
...@@ -419,3 +420,10 @@ func (c *nameCoordinator) Has(name string) bool { ...@@ -419,3 +420,10 @@ func (c *nameCoordinator) Has(name string) bool {
c.mu.RUnlock() c.mu.RUnlock()
return ok return ok
} }
// KnownACMECAs is a list of ACME directory endpoints of
// known, public, and trusted ACME-compatible certificate
// authorities.
var KnownACMECAs = []string{
"https://acme-v02.api.letsencrypt.org/directory",
}
...@@ -100,24 +100,31 @@ func (cg configGroup) GetConfigForClient(clientHello *tls.ClientHelloInfo) (*tls ...@@ -100,24 +100,31 @@ func (cg configGroup) GetConfigForClient(clientHello *tls.ClientHelloInfo) (*tls
// //
// This method is safe for use as a tls.Config.GetCertificate callback. // This method is safe for use as a tls.Config.GetCertificate callback.
func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
go diagnostics.Append("client_hello", struct { // TODO: We need to collect this in a heavily de-duplicating way
NoSNI bool `json:"no_sni,omitempty"` // It would also be nice to associate a handshake with the UA string (but that is only for HTTP server type)
CipherSuites []uint16 `json:"cipher_suites,omitempty"` // go diagnostics.Append("tls_client_hello", struct {
SupportedCurves []tls.CurveID `json:"curves,omitempty"` // NoSNI bool `json:"no_sni,omitempty"`
SupportedPoints []uint8 `json:"points,omitempty"` // CipherSuites []uint16 `json:"cipher_suites,omitempty"`
SignatureSchemes []tls.SignatureScheme `json:"sig_scheme,omitempty"` // SupportedCurves []tls.CurveID `json:"curves,omitempty"`
ALPN []string `json:"alpn,omitempty"` // SupportedPoints []uint8 `json:"points,omitempty"`
SupportedVersions []uint16 `json:"versions,omitempty"` // SignatureSchemes []tls.SignatureScheme `json:"sig_scheme,omitempty"`
}{ // ALPN []string `json:"alpn,omitempty"`
NoSNI: clientHello.ServerName == "", // SupportedVersions []uint16 `json:"versions,omitempty"`
CipherSuites: clientHello.CipherSuites, // }{
SupportedCurves: clientHello.SupportedCurves, // NoSNI: clientHello.ServerName == "",
SupportedPoints: clientHello.SupportedPoints, // CipherSuites: clientHello.CipherSuites,
SignatureSchemes: clientHello.SignatureSchemes, // SupportedCurves: clientHello.SupportedCurves,
ALPN: clientHello.SupportedProtos, // SupportedPoints: clientHello.SupportedPoints,
SupportedVersions: clientHello.SupportedVersions, // SignatureSchemes: clientHello.SignatureSchemes,
}) // ALPN: clientHello.SupportedProtos,
// SupportedVersions: clientHello.SupportedVersions,
// })
cert, err := cfg.getCertDuringHandshake(strings.ToLower(clientHello.ServerName), true, true) cert, err := cfg.getCertDuringHandshake(strings.ToLower(clientHello.ServerName), true, true)
if err == nil {
go diagnostics.Increment("tls_handshake_count")
} else {
go diagnostics.Append("tls_handshake_error", err.Error())
}
return &cert.Certificate, err return &cert.Certificate, err
} }
......
...@@ -28,6 +28,7 @@ import ( ...@@ -28,6 +28,7 @@ import (
"strings" "strings"
"github.com/mholt/caddy" "github.com/mholt/caddy"
"github.com/mholt/caddy/diagnostics"
) )
func init() { func init() {
...@@ -174,9 +175,11 @@ func setupTLS(c *caddy.Controller) error { ...@@ -174,9 +175,11 @@ func setupTLS(c *caddy.Controller) error {
case "max_certs": case "max_certs":
c.Args(&maxCerts) c.Args(&maxCerts)
config.OnDemand = true config.OnDemand = true
diagnostics.Increment("tls_on_demand_count")
case "ask": case "ask":
c.Args(&askURL) c.Args(&askURL)
config.OnDemand = true config.OnDemand = true
diagnostics.Increment("tls_on_demand_count")
case "dns": case "dns":
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) != 1 { if len(args) != 1 {
...@@ -251,6 +254,7 @@ func setupTLS(c *caddy.Controller) error { ...@@ -251,6 +254,7 @@ func setupTLS(c *caddy.Controller) error {
return c.Errf("Unable to load certificate and key files for '%s': %v", c.Key, err) return c.Errf("Unable to load certificate and key files for '%s': %v", c.Key, err)
} }
log.Printf("[INFO] Successfully loaded TLS assets from %s and %s", certificateFile, keyFile) log.Printf("[INFO] Successfully loaded TLS assets from %s and %s", certificateFile, keyFile)
diagnostics.Increment("tls_manual_cert_count")
} }
// load a directory of certificates, if specified // load a directory of certificates, if specified
...@@ -270,6 +274,7 @@ func setupTLS(c *caddy.Controller) error { ...@@ -270,6 +274,7 @@ func setupTLS(c *caddy.Controller) error {
if err != nil { if err != nil {
return fmt.Errorf("self-signed: %v", err) return fmt.Errorf("self-signed: %v", err)
} }
diagnostics.Increment("tls_self_signed_count")
} }
return nil return nil
...@@ -350,6 +355,7 @@ func loadCertsInDir(cfg *Config, c *caddy.Controller, dir string) error { ...@@ -350,6 +355,7 @@ func loadCertsInDir(cfg *Config, c *caddy.Controller, dir string) error {
return c.Errf("%s: failed to load cert and key for '%s': %v", path, c.Key, err) return c.Errf("%s: failed to load cert and key for '%s': %v", path, c.Key, err)
} }
log.Printf("[INFO] Successfully loaded TLS assets from %s", path) log.Printf("[INFO] Successfully loaded TLS assets from %s", path)
diagnostics.Increment("tls_manual_cert_count")
} }
return nil return nil
}) })
......
...@@ -33,7 +33,7 @@ func Init(instanceID uuid.UUID) { ...@@ -33,7 +33,7 @@ func Init(instanceID uuid.UUID) {
panic("already initialized") panic("already initialized")
} }
if str := instanceID.String(); str == "" || if str := instanceID.String(); str == "" ||
instanceID.String() == "00000000-0000-0000-0000-000000000000" { str == "00000000-0000-0000-0000-000000000000" {
panic("empty UUID") panic("empty UUID")
} }
instanceUUID = instanceID instanceUUID = instanceID
...@@ -73,6 +73,10 @@ func StartEmitting() { ...@@ -73,6 +73,10 @@ func StartEmitting() {
// //
// It is a no-op if the package was never initialized // It is a no-op if the package was never initialized
// or if emitting was never started. // or if emitting was never started.
//
// NOTE: This function is blocking. Run in a goroutine if
// you want to guarantee no blocking at critical times
// like exiting the program.
func StopEmitting() { func StopEmitting() {
if !enabled { if !enabled {
return return
...@@ -83,7 +87,12 @@ func StopEmitting() { ...@@ -83,7 +87,12 @@ func StopEmitting() {
return return
} }
updateTimerMu.Unlock() updateTimerMu.Unlock()
logEmit(true) logEmit(true) // likely too early; may take minutes to return
}
// Reset empties the current payload buffer.
func Reset() {
resetBuffer()
} }
// Set puts a value in the buffer to be included // Set puts a value in the buffer to be included
...@@ -142,7 +151,7 @@ func Append(key string, value interface{}) { ...@@ -142,7 +151,7 @@ func Append(key string, value interface{}) {
bufferMu.Unlock() bufferMu.Unlock()
} }
// AppendUnique adds value to a set namedkey. // AppendUnique adds value to a set named key.
// Set items are unordered. Values in the set // Set items are unordered. Values in the set
// are unique, but how many times they are // are unique, but how many times they are
// appended is counted. // appended is counted.
...@@ -178,24 +187,23 @@ func AppendUnique(key string, value interface{}) { ...@@ -178,24 +187,23 @@ func AppendUnique(key string, value interface{}) {
bufferMu.Unlock() bufferMu.Unlock()
} }
// Increment adds 1 to a value named key. // Add adds amount to a value named key.
// If it does not exist, it is created with // If it does not exist, it is created with
// a value of 1. If key maps to a type that // a value of 1. If key maps to a type that
// is not an integer, a panic is logged, // is not an integer, a panic is logged,
// and this is a no-op. // and this is a no-op.
func Increment(key string) { func Add(key string, amount int) {
incrementOrDecrement(key, true) atomicAdd(key, amount)
} }
// Decrement is the same as increment except // Increment is a shortcut for Add(key, 1)
// it subtracts 1. func Increment(key string) {
func Decrement(key string) { atomicAdd(key, 1)
incrementOrDecrement(key, false)
} }
// inc == true: increment // atomicAdd adds amount (negative to subtract)
// inc == false: decrement // to key.
func incrementOrDecrement(key string, inc bool) { func atomicAdd(key string, amount int) {
if !enabled { if !enabled {
return return
} }
...@@ -214,10 +222,6 @@ func incrementOrDecrement(key string, inc bool) { ...@@ -214,10 +222,6 @@ func incrementOrDecrement(key string, inc bool) {
} }
bufferItemCount++ bufferItemCount++
} }
if inc { buffer[key] = intVal + amount
buffer[key] = intVal + 1
} else {
buffer[key] = intVal - 1
}
bufferMu.Unlock() bufferMu.Unlock()
} }
...@@ -48,14 +48,16 @@ import ( ...@@ -48,14 +48,16 @@ import (
) )
// logEmit calls emit and then logs the error, if any. // logEmit calls emit and then logs the error, if any.
// See docs for emit.
func logEmit(final bool) { func logEmit(final bool) {
err := emit(final) err := emit(final)
if err != nil { if err != nil {
log.Printf("[ERROR] Sending diganostics: %v", err) log.Printf("[ERROR] Sending diagnostics: %v", err)
} }
} }
// emit sends an update to the diagnostics server. // emit sends an update to the diagnostics server.
// Set final to true if this is the last call to emit.
// If final is true, no future updates will be scheduled. // If final is true, no future updates will be scheduled.
// Otherwise, the next update will be scheduled. // Otherwise, the next update will be scheduled.
func emit(final bool) error { func emit(final bool) error {
...@@ -136,9 +138,11 @@ func emit(final bool) error { ...@@ -136,9 +138,11 @@ func emit(final bool) error {
reply.NextUpdate = time.Duration(ra) * time.Second reply.NextUpdate = time.Duration(ra) * time.Second
} }
} }
log.Printf("[NOTICE] Sending diagnostics: we were too early; waiting %s before trying again", reply.NextUpdate) if !final {
time.Sleep(reply.NextUpdate) log.Printf("[NOTICE] Sending diagnostics: we were too early; waiting %s before trying again", reply.NextUpdate)
continue time.Sleep(reply.NextUpdate)
continue
}
} else if resp.StatusCode >= 400 { } else if resp.StatusCode >= 400 {
err = fmt.Errorf("diagnostics server returned status code %d", resp.StatusCode) err = fmt.Errorf("diagnostics server returned status code %d", resp.StatusCode)
continue continue
...@@ -146,7 +150,7 @@ func emit(final bool) error { ...@@ -146,7 +150,7 @@ func emit(final bool) error {
break break
} }
if err == nil { if err == nil && !final {
// (remember, if there was an error, we return it // (remember, if there was an error, we return it
// below, so it WILL get logged if it's supposed to) // below, so it WILL get logged if it's supposed to)
log.Println("[INFO] Sending diagnostics: success") log.Println("[INFO] Sending diagnostics: success")
...@@ -181,13 +185,7 @@ func emit(final bool) error { ...@@ -181,13 +185,7 @@ func emit(final bool) error {
// resulting byte slice is lost, the payload is // resulting byte slice is lost, the payload is
// gone with it. // gone with it.
func makePayloadAndResetBuffer() ([]byte, error) { func makePayloadAndResetBuffer() ([]byte, error) {
// make a local pointer to the buffer, then reset bufCopy := resetBuffer()
// the buffer to an empty map to clear it out
bufferMu.Lock()
bufCopy := buffer
buffer = make(map[string]interface{})
bufferItemCount = 0
bufferMu.Unlock()
// encode payload in preparation for transmission // encode payload in preparation for transmission
payload := Payload{ payload := Payload{
...@@ -198,6 +196,21 @@ func makePayloadAndResetBuffer() ([]byte, error) { ...@@ -198,6 +196,21 @@ func makePayloadAndResetBuffer() ([]byte, error) {
return json.Marshal(payload) return json.Marshal(payload)
} }
// resetBuffer makes a local pointer to the buffer,
// then resets the buffer by assigning to be a newly-
// made value to clear it out, then sets the buffer
// item count to 0. It returns the copied pointer to
// the original map so the old buffer value can be
// used locally.
func resetBuffer() map[string]interface{} {
bufferMu.Lock()
bufCopy := buffer
buffer = make(map[string]interface{})
bufferItemCount = 0
bufferMu.Unlock()
return bufCopy
}
// Response contains the body of a response from the // Response contains the body of a response from the
// diagnostics server. // diagnostics server.
type Response struct { type Response struct {
...@@ -222,10 +235,28 @@ type Payload struct { ...@@ -222,10 +235,28 @@ type Payload struct {
// The UTC timestamp of the transmission // The UTC timestamp of the transmission
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
// The timestamp before which the next update is expected
// (NOT populated by client - the server fills this in
// before it stores the data)
ExpectNext time.Time `json:"expect_next,omitempty"`
// The metrics // The metrics
Data map[string]interface{} `json:"data,omitempty"` Data map[string]interface{} `json:"data,omitempty"`
} }
// Int returns the value of the data keyed by key
// if it is an integer; otherwise it returns 0.
func (p Payload) Int(key string) int {
val, _ := p.Data[key]
switch p.Data[key].(type) {
case int:
return val.(int)
case float64: // after JSON-decoding, int becomes float64...
return int(val.(float64))
}
return 0
}
// countingSet implements a set that counts how many // countingSet implements a set that counts how many
// times a key is inserted. It marshals to JSON in a // times a key is inserted. It marshals to JSON in a
// way such that keys are converted to values next // way such that keys are converted to values next
...@@ -272,6 +303,7 @@ var ( ...@@ -272,6 +303,7 @@ var (
// instanceUUID is the ID of the current instance. // instanceUUID is the ID of the current instance.
// This MUST be set to emit diagnostics. // This MUST be set to emit diagnostics.
// This MUST NOT be openly exposed to clients, for privacy.
instanceUUID uuid.UUID instanceUUID uuid.UUID
// enabled indicates whether the package has // enabled indicates whether the package has
......
...@@ -19,6 +19,8 @@ import ( ...@@ -19,6 +19,8 @@ import (
"os" "os"
"os/signal" "os/signal"
"sync" "sync"
"github.com/mholt/caddy/diagnostics"
) )
// TrapSignals create signal handlers for all applicable signals for this // TrapSignals create signal handlers for all applicable signals for this
...@@ -52,6 +54,9 @@ func trapSignalsCrossPlatform() { ...@@ -52,6 +54,9 @@ func trapSignalsCrossPlatform() {
log.Println("[INFO] SIGINT: Shutting down") log.Println("[INFO] SIGINT: Shutting down")
diagnostics.AppendUnique("sigtrap", "SIGINT")
go diagnostics.StopEmitting() // not guaranteed to finish in time; that's OK (just don't block!)
// important cleanup actions before shutdown callbacks // important cleanup actions before shutdown callbacks
for _, f := range OnProcessExit { for _, f := range OnProcessExit {
f() f()
......
...@@ -21,6 +21,8 @@ import ( ...@@ -21,6 +21,8 @@ import (
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/mholt/caddy/diagnostics"
) )
// trapSignalsPosix captures POSIX-only signals. // trapSignalsPosix captures POSIX-only signals.
...@@ -49,10 +51,15 @@ func trapSignalsPosix() { ...@@ -49,10 +51,15 @@ func trapSignalsPosix() {
log.Printf("[ERROR] SIGTERM stop: %v", err) log.Printf("[ERROR] SIGTERM stop: %v", err)
exitCode = 3 exitCode = 3
} }
diagnostics.AppendUnique("sigtrap", "SIGTERM")
go diagnostics.StopEmitting() // won't finish in time, but that's OK - just don't block
os.Exit(exitCode) os.Exit(exitCode)
case syscall.SIGUSR1: case syscall.SIGUSR1:
log.Println("[INFO] SIGUSR1: Reloading") log.Println("[INFO] SIGUSR1: Reloading")
go diagnostics.AppendUnique("sigtrap", "SIGUSR1")
// Start with the existing Caddyfile // Start with the existing Caddyfile
caddyfileToUse, inst, err := getCurrentCaddyfile() caddyfileToUse, inst, err := getCurrentCaddyfile()
...@@ -84,12 +91,14 @@ func trapSignalsPosix() { ...@@ -84,12 +91,14 @@ func trapSignalsPosix() {
case syscall.SIGUSR2: case syscall.SIGUSR2:
log.Println("[INFO] SIGUSR2: Upgrading") log.Println("[INFO] SIGUSR2: Upgrading")
go diagnostics.AppendUnique("sigtrap", "SIGUSR2")
if err := Upgrade(); err != nil { if err := Upgrade(); err != nil {
log.Printf("[ERROR] SIGUSR2: upgrading: %v", err) log.Printf("[ERROR] SIGUSR2: upgrading: %v", err)
} }
case syscall.SIGHUP: case syscall.SIGHUP:
// ignore; this signal is sometimes sent outside of the user's control // ignore; this signal is sometimes sent outside of the user's control
go diagnostics.AppendUnique("sigtrap", "SIGHUP")
} }
} }
}() }()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment