TUN-8735: add managed/local log collection
## Summary Adds a log collector for the managed/local runtimes. Closes TUN-8735 TUN-8736
This commit is contained in:
parent
f85c0f1cc0
commit
a6f9e68739
|
@ -127,7 +127,9 @@ var (
|
|||
"most likely you already have a conflicting record there. You can also rerun this command with --%s to overwrite "+
|
||||
"any existing DNS records for this hostname.", overwriteDNSFlag)
|
||||
deprecatedClassicTunnelErr = fmt.Errorf("Classic tunnels have been deprecated, please use Named Tunnels. (https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/)")
|
||||
nonSecretFlagsList = []string{
|
||||
// TODO: TUN-8756 the list below denotes the flags that do not possess any kind of sensitive information
|
||||
// however this approach is not maintainble in the long-term.
|
||||
nonSecretFlagsList = []string{
|
||||
"config",
|
||||
"autoupdate-freq",
|
||||
"no-autoupdate",
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package diagnostic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
)
|
||||
|
||||
const configurationEndpoint = "diag/configuration"
|
||||
|
||||
type httpClient struct {
|
||||
http.Client
|
||||
baseURL url.URL
|
||||
}
|
||||
|
||||
func NewHTTPClient(baseURL url.URL) *httpClient {
|
||||
httpTransport := http.Transport{
|
||||
TLSHandshakeTimeout: defaultTimeout,
|
||||
ResponseHeaderTimeout: defaultTimeout,
|
||||
}
|
||||
|
||||
return &httpClient{
|
||||
http.Client{
|
||||
Transport: &httpTransport,
|
||||
Timeout: defaultTimeout,
|
||||
},
|
||||
baseURL,
|
||||
}
|
||||
}
|
||||
|
||||
func (client *httpClient) GET(ctx context.Context, url string) (*http.Response, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating GET request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Add("Accept", "application/json;version=1")
|
||||
|
||||
response, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error GET request: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
type LogConfiguration struct {
|
||||
logFile string
|
||||
logDirectory string
|
||||
uid int // the uid of the user that started cloudflared
|
||||
}
|
||||
|
||||
func (client *httpClient) GetLogConfiguration(ctx context.Context) (*LogConfiguration, error) {
|
||||
endpoint, err := url.JoinPath(client.baseURL.String(), configurationEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing URL: %w", err)
|
||||
}
|
||||
|
||||
response, err := client.GET(ctx, endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
var data map[string]string
|
||||
if err := json.NewDecoder(response.Body).Decode(&data); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode body: %w", err)
|
||||
}
|
||||
|
||||
uidStr, exists := data[configurationKeyUID]
|
||||
if !exists {
|
||||
return nil, ErrKeyNotFound
|
||||
}
|
||||
|
||||
uid, err := strconv.Atoi(uidStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error convertin pid to int: %w", err)
|
||||
}
|
||||
|
||||
logFile, exists := data[logger.LogFileFlag]
|
||||
if exists {
|
||||
return &LogConfiguration{logFile, "", uid}, nil
|
||||
}
|
||||
|
||||
logDirectory, exists := data[logger.LogDirectoryFlag]
|
||||
if exists {
|
||||
return &LogConfiguration{"", logDirectory, uid}, nil
|
||||
}
|
||||
|
||||
return nil, ErrKeyNotFound
|
||||
}
|
||||
|
||||
type HTTPClient interface {
|
||||
GetLogConfiguration(ctx context.Context) (LogConfiguration, error)
|
||||
}
|
|
@ -3,10 +3,12 @@ package diagnostic
|
|||
import "time"
|
||||
|
||||
const (
|
||||
defaultCollectorTimeout = time.Second * 10 // This const define the timeout value of a collector operation.
|
||||
collectorField = "collector" // used for logging purposes
|
||||
systemCollectorName = "system" // used for logging purposes
|
||||
tunnelStateCollectorName = "tunnelState" // used for logging purposes
|
||||
configurationCollectorName = "configuration" // used for logging purposes
|
||||
configurationKeyUid = "uid"
|
||||
defaultCollectorTimeout = time.Second * 10 // This const define the timeout value of a collector operation.
|
||||
collectorField = "collector" // used for logging purposes
|
||||
systemCollectorName = "system" // used for logging purposes
|
||||
tunnelStateCollectorName = "tunnelState" // used for logging purposes
|
||||
configurationCollectorName = "configuration" // used for logging purposes
|
||||
defaultTimeout = 15 * time.Second // timeout for the collectors
|
||||
twoWeeksOffset = -14 * 24 * time.Hour // maximum offset for the logs
|
||||
configurationKeyUID = "uid" // Key used to set and get the UID value from the configuration map
|
||||
)
|
||||
|
|
|
@ -5,12 +5,17 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
// Error used when there is no log directory available.
|
||||
ErrManagedLogNotFound = errors.New("managed log directory not found")
|
||||
// Error used when one key is not found.
|
||||
ErrMustNotBeEmpty = errors.New("provided argument is empty")
|
||||
// Error used when parsing the fields of the output of collector.
|
||||
ErrInsufficientLines = errors.New("insufficient lines")
|
||||
// Error used when parsing the lines of the output of collector.
|
||||
ErrInsuficientFields = errors.New("insufficient fields")
|
||||
// Error used when given key is not found while parsing KV.
|
||||
ErrKeyNotFound = errors.New("key not found")
|
||||
// Error used when tehre is no disk volume information available
|
||||
ErrNoVolumeFound = errors.New("No disk volume information found")
|
||||
// Error used when there is no disk volume information available.
|
||||
ErrNoVolumeFound = errors.New("no disk volume information found")
|
||||
ErrNoPathAvailable = errors.New("no path available")
|
||||
)
|
||||
|
|
|
@ -166,7 +166,7 @@ func (handler *Handler) ConfigurationHandler(writer http.ResponseWriter, _ *http
|
|||
// The UID is included to help the
|
||||
// diagnostic tool to understand
|
||||
// if this instance is managed or not.
|
||||
flags[configurationKeyUid] = strconv.Itoa(os.Getuid())
|
||||
flags[configurationKeyUID] = strconv.Itoa(os.Getuid())
|
||||
encoder := json.NewEncoder(writer)
|
||||
|
||||
err := encoder.Encode(flags)
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package diagnostic
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// Represents the path of the log file or log directory.
|
||||
// This struct is meant to give some ergonimics regarding
|
||||
// the logging information.
|
||||
type LogInformation struct {
|
||||
path string // path to a file or directory
|
||||
wasCreated bool // denotes if `path` was created
|
||||
isDirectory bool // denotes if `path` is a directory
|
||||
}
|
||||
|
||||
func NewLogInformation(
|
||||
path string,
|
||||
wasCreated bool,
|
||||
isDirectory bool,
|
||||
) *LogInformation {
|
||||
return &LogInformation{
|
||||
path,
|
||||
wasCreated,
|
||||
isDirectory,
|
||||
}
|
||||
}
|
||||
|
||||
type LogCollector interface {
|
||||
// This function is responsible for returning a path to a single file
|
||||
// whose contents are the logs of a cloudflared instance.
|
||||
// A new file may be create by a LogCollector, thus, its the caller
|
||||
// responsibility to remove the newly create file.
|
||||
Collect(ctx context.Context) (*LogInformation, error)
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package diagnostic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
linuxManagedLogsPath = "/var/log/cloudflared.err"
|
||||
darwinManagedLogsPath = "/Library/Logs/com.cloudflare.cloudflared.err.log"
|
||||
)
|
||||
|
||||
type HostLogCollector struct {
|
||||
client HTTPClient
|
||||
}
|
||||
|
||||
func NewHostLogCollector(client HTTPClient) *HostLogCollector {
|
||||
return &HostLogCollector{
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
func getServiceLogPath() (string, error) {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
{
|
||||
path := darwinManagedLogsPath
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
userHomeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error getting user home: %w", err)
|
||||
}
|
||||
|
||||
return filepath.Join(userHomeDir, darwinManagedLogsPath), nil
|
||||
}
|
||||
case "linux":
|
||||
{
|
||||
return linuxManagedLogsPath, nil
|
||||
}
|
||||
default:
|
||||
return "", ErrManagedLogNotFound
|
||||
}
|
||||
}
|
||||
|
||||
func (collector *HostLogCollector) Collect(ctx context.Context) (*LogInformation, error) {
|
||||
logConfiguration, err := collector.client.GetLogConfiguration(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting log configuration: %w", err)
|
||||
}
|
||||
|
||||
if logConfiguration.uid == 0 {
|
||||
path, err := getServiceLogPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewLogInformation(path, false, false), nil
|
||||
}
|
||||
|
||||
if logConfiguration.logFile != "" {
|
||||
return NewLogInformation(logConfiguration.logFile, false, false), nil
|
||||
} else if logConfiguration.logDirectory != "" {
|
||||
return NewLogInformation(logConfiguration.logDirectory, false, true), nil
|
||||
}
|
||||
|
||||
return nil, ErrMustNotBeEmpty
|
||||
}
|
Loading…
Reference in New Issue