mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-02 23:31:08 +08:00
* feat: debug service
* fix(service): lint and test assertion
* refactor: code review suggestions
* chore(service): update change log level log message
* fix(service): wrong error assertion
* fix(service): unable to set trace log level
* chore(utils): rename SupportedLogLevelsString
* refactor(teleport): change `seconds` default value
* fix(teleport): remove trailing line on profiles file
* chore(teleport): add profile examples
* refactor(service): use marshal/unmarshal log level functions
* fix(teleport): fix tarball file timestamps
* refactor: move debug service + constants to a separate package
* test(debug): move request to a helper
* chore(debug): remove leftover file
* chore(log): typo
* refactor: move package from lib/debug to lib/srv/debug
* refactor: code review suggestions
* fix(service): socket file consistency
* test(common): use one temporary directory per socket test
* chore(tool): update debug service connection error message
* refactor(debug): use enhanced serve mux
* chore(teleport): fix usage examples
* Apply suggestions from code review
Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com>
* refactor: code review suggestions
* refactor(debug): move marshal/unmarshal text log level to debug
* refactor: code review suggestions
* refactor: move debug service client to lib/client/debug/
* refactor: code review changes
* feat(debug): progress on collect profiles
* refactor: code review suggestions
* chore(gomod): update
* fix(debug): set log level validations
* refactor(debug): move client interface
* feat(debug): add limit reader for collect profiles
* refactor(debug): remove progress bar
* Revert "chore(gomod): update"
This reverts commit 8dbebbf69f.
* refactor: remove debug service endpoints constants
* test(service): fix wrong constant
* refactor(debug): remove collect profiles limit reader
* chore(debug): make client struct public
* refactor(debug): change collect time to 0
* test(service): deflake test
* test(service): add missing auth config
* test(debug): fix missing assertion
---------
Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com>
161 lines
4.1 KiB
Go
161 lines
4.1 KiB
Go
// Teleport
|
|
// Copyright (C) 2024 Gravitational, Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package debug
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
|
|
"github.com/gravitational/trace"
|
|
|
|
apidefaults "github.com/gravitational/teleport/api/defaults"
|
|
)
|
|
|
|
// SupportedProfiles list of supported pprof profiles that can be collected.
|
|
// This list is composed by runtime/pprof.Profile and http/pprof definitions.
|
|
var SupportedProfiles = map[string]struct{}{
|
|
"allocs": {},
|
|
"block": {},
|
|
"cmdline": {},
|
|
"goroutine": {},
|
|
"heap": {},
|
|
"mutex": {},
|
|
"profile": {},
|
|
"threadcreate": {},
|
|
"trace": {},
|
|
}
|
|
|
|
// Client represents the debug service client.
|
|
type Client struct {
|
|
clt *http.Client
|
|
}
|
|
|
|
// NewClient generates a new debug service client.
|
|
func NewClient(socketPath string) *Client {
|
|
return &Client{
|
|
clt: &http.Client{
|
|
Timeout: apidefaults.DefaultIOTimeout,
|
|
Transport: &http.Transport{
|
|
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
|
|
return net.Dial("unix", socketPath)
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// SetLogLevel changes the application's log level and a change status message.
|
|
func (c *Client) SetLogLevel(ctx context.Context, level string) (string, error) {
|
|
resp, err := c.do(ctx, http.MethodPut, url.URL{Path: "/log-level"}, []byte(level))
|
|
if err != nil {
|
|
return "", trace.Wrap(err)
|
|
}
|
|
|
|
respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024))
|
|
defer resp.Body.Close()
|
|
if err != nil {
|
|
return "", trace.Wrap(err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", trace.BadParameter("Unable to change log level: %s", respBody)
|
|
}
|
|
|
|
return string(respBody), nil
|
|
}
|
|
|
|
// GetLogLevel fetches the current log level.
|
|
func (c *Client) GetLogLevel(ctx context.Context) (string, error) {
|
|
resp, err := c.do(ctx, http.MethodGet, url.URL{Path: "/log-level"}, nil)
|
|
if err != nil {
|
|
return "", trace.Wrap(err)
|
|
}
|
|
|
|
respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024))
|
|
defer resp.Body.Close()
|
|
if err != nil {
|
|
return "", trace.Wrap(err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", trace.BadParameter("Unable to fetch log level: %s", respBody)
|
|
}
|
|
|
|
return string(respBody), nil
|
|
}
|
|
|
|
// CollectProfile collects a pprof profile.
|
|
func (c *Client) CollectProfile(ctx context.Context, profileName string, seconds int) ([]byte, error) {
|
|
u := url.URL{
|
|
Path: "/debug/pprof/" + profileName,
|
|
}
|
|
|
|
if _, ok := SupportedProfiles[profileName]; !ok {
|
|
return nil, trace.BadParameter("%q profile not supported", profileName)
|
|
}
|
|
|
|
if seconds > 0 {
|
|
qs := url.Values{}
|
|
qs.Add("seconds", strconv.Itoa(seconds))
|
|
u.RawQuery = qs.Encode()
|
|
}
|
|
|
|
resp, err := c.do(ctx, http.MethodGet, u, nil)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
result, err := io.ReadAll(resp.Body)
|
|
defer resp.Body.Close()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, trace.BadParameter("Unable to collect profile %q: %s", profileName, result)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (c *Client) do(ctx context.Context, method string, u url.URL, body []byte) (*http.Response, error) {
|
|
u.Scheme = "http"
|
|
u.Host = "debug"
|
|
|
|
var bodyReader io.Reader
|
|
if body != nil {
|
|
bodyReader = bytes.NewBuffer(body)
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, method, u.String(), bodyReader)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
resp, err := c.clt.Do(req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(trace.ConvertSystemError(err))
|
|
}
|
|
|
|
return resp, nil
|
|
}
|