logs: allow disabling log collection in jobspec (#16962)
Some Nomad users ship application logs out-of-band via syslog. For these users having `logmon` (and `docker_logger`) running is unnecessary overhead. Allow disabling the logmon and pointing the task's stdout/stderr to /dev/null. This changeset is the first of several incremental improvements to log collection short of full-on logging plugins. The next step will likely be to extend the internal-only task driver configuration so that cluster administrators can turn off log collection for the entire driver. --- Fixes: #11175 Co-authored-by: Thomas Weber <towe75@googlemail.com>
This commit is contained in:
parent
379497a484
commit
72cbe53f19
3
.changelog/16962.txt
Normal file
3
.changelog/16962.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
jobspec: Added option for disabling task log collection in the `logs` block
|
||||||
|
```
|
|
@ -641,12 +641,14 @@ func (g *TaskGroup) AddSpread(s *Spread) *TaskGroup {
|
||||||
type LogConfig struct {
|
type LogConfig struct {
|
||||||
MaxFiles *int `mapstructure:"max_files" hcl:"max_files,optional"`
|
MaxFiles *int `mapstructure:"max_files" hcl:"max_files,optional"`
|
||||||
MaxFileSizeMB *int `mapstructure:"max_file_size" hcl:"max_file_size,optional"`
|
MaxFileSizeMB *int `mapstructure:"max_file_size" hcl:"max_file_size,optional"`
|
||||||
|
Enabled *bool `mapstructure:"enabled" hcl:"enabled,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultLogConfig() *LogConfig {
|
func DefaultLogConfig() *LogConfig {
|
||||||
return &LogConfig{
|
return &LogConfig{
|
||||||
MaxFiles: pointerOf(10),
|
MaxFiles: pointerOf(10),
|
||||||
MaxFileSizeMB: pointerOf(10),
|
MaxFileSizeMB: pointerOf(10),
|
||||||
|
Enabled: pointerOf(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,6 +659,9 @@ func (l *LogConfig) Canonicalize() {
|
||||||
if l.MaxFileSizeMB == nil {
|
if l.MaxFileSizeMB == nil {
|
||||||
l.MaxFileSizeMB = pointerOf(10)
|
l.MaxFileSizeMB = pointerOf(10)
|
||||||
}
|
}
|
||||||
|
if l.Enabled == nil {
|
||||||
|
l.Enabled = pointerOf(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DispatchPayloadConfig configures how a task gets its input from a job dispatch
|
// DispatchPayloadConfig configures how a task gets its input from a job dispatch
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
@ -45,6 +46,7 @@ type logmonHook struct {
|
||||||
|
|
||||||
type logmonHookConfig struct {
|
type logmonHookConfig struct {
|
||||||
logDir string
|
logDir string
|
||||||
|
enabled bool
|
||||||
stdoutFifo string
|
stdoutFifo string
|
||||||
stderrFifo string
|
stderrFifo string
|
||||||
}
|
}
|
||||||
|
@ -59,10 +61,19 @@ func newLogMonHook(tr *TaskRunner, logger hclog.Logger) *logmonHook {
|
||||||
return hook
|
return hook
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLogMonHookConfig(taskName, logDir string) *logmonHookConfig {
|
func newLogMonHookConfig(taskName string, logCfg *structs.LogConfig, logDir string) *logmonHookConfig {
|
||||||
cfg := &logmonHookConfig{
|
cfg := &logmonHookConfig{
|
||||||
logDir: logDir,
|
logDir: logDir,
|
||||||
|
enabled: logCfg.Enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If logging is disabled configure task's stdout/err to point to devnull
|
||||||
|
if !logCfg.Enabled {
|
||||||
|
cfg.stdoutFifo = os.DevNull
|
||||||
|
cfg.stderrFifo = os.DevNull
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
id := uuid.Generate()[:8]
|
id := uuid.Generate()[:8]
|
||||||
cfg.stdoutFifo = fmt.Sprintf("//./pipe/%s-%s.stdout", taskName, id)
|
cfg.stdoutFifo = fmt.Sprintf("//./pipe/%s-%s.stdout", taskName, id)
|
||||||
|
@ -105,9 +116,7 @@ func reattachConfigFromHookData(data map[string]string) (*plugin.ReattachConfig,
|
||||||
|
|
||||||
func (h *logmonHook) Prestart(ctx context.Context,
|
func (h *logmonHook) Prestart(ctx context.Context,
|
||||||
req *interfaces.TaskPrestartRequest, resp *interfaces.TaskPrestartResponse) error {
|
req *interfaces.TaskPrestartRequest, resp *interfaces.TaskPrestartResponse) error {
|
||||||
|
if !h.isLoggingEnabled() {
|
||||||
if h.isLoggingDisabled() {
|
|
||||||
h.logger.Debug("logging is disabled by driver")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,14 +151,27 @@ func (h *logmonHook) Prestart(ctx context.Context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *logmonHook) isLoggingDisabled() bool {
|
func (h *logmonHook) isLoggingEnabled() bool {
|
||||||
ic, ok := h.runner.driver.(drivers.InternalCapabilitiesDriver)
|
if !h.config.enabled {
|
||||||
if !ok {
|
h.logger.Debug("log collection is disabled by task")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// internal plugins have access to a capability to disable logging and
|
||||||
|
// metrics via a private interface; this is an "experimental" interface and
|
||||||
|
// currently only the docker driver exposes this to users.
|
||||||
|
ic, ok := h.runner.driver.(drivers.InternalCapabilitiesDriver)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
caps := ic.InternalCapabilities()
|
caps := ic.InternalCapabilities()
|
||||||
return caps.DisableLogCollection
|
if caps.DisableLogCollection {
|
||||||
|
h.logger.Debug("log collection is disabled by driver")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *logmonHook) prestartOneLoop(ctx context.Context, req *interfaces.TaskPrestartRequest) error {
|
func (h *logmonHook) prestartOneLoop(ctx context.Context, req *interfaces.TaskPrestartRequest) error {
|
||||||
|
@ -197,6 +219,9 @@ func (h *logmonHook) prestartOneLoop(ctx context.Context, req *interfaces.TaskPr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *logmonHook) Stop(_ context.Context, req *interfaces.TaskStopRequest, _ *interfaces.TaskStopResponse) error {
|
func (h *logmonHook) Stop(_ context.Context, req *interfaces.TaskStopRequest, _ *interfaces.TaskStopResponse) error {
|
||||||
|
if !h.isLoggingEnabled() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// It's possible that Stop was called without calling Prestart on agent
|
// It's possible that Stop was called without calling Prestart on agent
|
||||||
// restarts. Attempt to reattach to an existing logmon.
|
// restarts. Attempt to reattach to an existing logmon.
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/hashicorp/nomad/helper/testlog"
|
"github.com/hashicorp/nomad/helper/testlog"
|
||||||
"github.com/hashicorp/nomad/nomad/mock"
|
"github.com/hashicorp/nomad/nomad/mock"
|
||||||
pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
|
pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
)
|
)
|
||||||
|
@ -69,7 +70,7 @@ func TestTaskRunner_LogmonHook_StartStop(t *testing.T) {
|
||||||
|
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
hookConf := newLogMonHookConfig(task.Name, dir)
|
hookConf := newLogMonHookConfig(task.Name, task.LogConfig, dir)
|
||||||
runner := &TaskRunner{logmonHookConfig: hookConf}
|
runner := &TaskRunner{logmonHookConfig: hookConf}
|
||||||
hook := newLogMonHook(runner, testlog.HCLogger(t))
|
hook := newLogMonHook(runner, testlog.HCLogger(t))
|
||||||
|
|
||||||
|
@ -103,3 +104,52 @@ func TestTaskRunner_LogmonHook_StartStop(t *testing.T) {
|
||||||
}
|
}
|
||||||
require.NoError(t, hook.Stop(context.Background(), &stopReq, nil))
|
require.NoError(t, hook.Stop(context.Background(), &stopReq, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTaskRunner_LogmonHook_Disabled asserts that no logmon running or expected
|
||||||
|
// by any of the lifecycle hooks.
|
||||||
|
func TestTaskRunner_LogmonHook_Disabled(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
|
||||||
|
alloc := mock.BatchAlloc()
|
||||||
|
task := alloc.Job.TaskGroups[0].Tasks[0]
|
||||||
|
task.LogConfig.Enabled = false
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
hookConf := newLogMonHookConfig(task.Name, task.LogConfig, dir)
|
||||||
|
runner := &TaskRunner{logmonHookConfig: hookConf}
|
||||||
|
hook := newLogMonHook(runner, testlog.HCLogger(t))
|
||||||
|
|
||||||
|
req := interfaces.TaskPrestartRequest{Task: task}
|
||||||
|
resp := interfaces.TaskPrestartResponse{}
|
||||||
|
|
||||||
|
// First prestart should not set reattach key and never be Done.
|
||||||
|
must.NoError(t, hook.Prestart(context.Background(), &req, &resp))
|
||||||
|
t.Cleanup(func() { hook.Stop(context.Background(), nil, nil) })
|
||||||
|
|
||||||
|
must.False(t, resp.Done)
|
||||||
|
hookData, ok := resp.State[logmonReattachKey]
|
||||||
|
must.False(t, ok)
|
||||||
|
must.Eq(t, "", hookData)
|
||||||
|
|
||||||
|
// Running prestart again should still be a noop
|
||||||
|
req.PreviousState = map[string]string{}
|
||||||
|
must.NoError(t, hook.Prestart(context.Background(), &req, &resp))
|
||||||
|
|
||||||
|
must.False(t, resp.Done)
|
||||||
|
hookData, ok = resp.State[logmonReattachKey]
|
||||||
|
must.False(t, ok)
|
||||||
|
must.Eq(t, "", hookData)
|
||||||
|
|
||||||
|
// PreviousState should always be initialized by the caller, but just
|
||||||
|
// belt-and-suspenders for this test to ensure we can't panic on this code
|
||||||
|
// path
|
||||||
|
req.PreviousState = nil
|
||||||
|
must.NoError(t, hook.Prestart(context.Background(), &req, &resp))
|
||||||
|
|
||||||
|
// Running stop should not error even with no running logmon
|
||||||
|
stopReq := interfaces.TaskStopRequest{
|
||||||
|
ExistingState: maps.Clone(resp.State),
|
||||||
|
}
|
||||||
|
must.NoError(t, hook.Stop(context.Background(), &stopReq, nil))
|
||||||
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ func TestTaskRunner_LogmonHook_StartCrashStop(t *testing.T) {
|
||||||
|
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
hookConf := newLogMonHookConfig(task.Name, dir)
|
hookConf := newLogMonHookConfig(task.Name, task.LogConfig, dir)
|
||||||
runner := &TaskRunner{logmonHookConfig: hookConf}
|
runner := &TaskRunner{logmonHookConfig: hookConf}
|
||||||
hook := newLogMonHook(runner, testlog.HCLogger(t))
|
hook := newLogMonHook(runner, testlog.HCLogger(t))
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ func TestTaskRunner_LogmonHook_ShutdownMidStart(t *testing.T) {
|
||||||
|
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
hookConf := newLogMonHookConfig(task.Name, dir)
|
hookConf := newLogMonHookConfig(task.Name, task.LogConfig, dir)
|
||||||
runner := &TaskRunner{logmonHookConfig: hookConf}
|
runner := &TaskRunner{logmonHookConfig: hookConf}
|
||||||
hook := newLogMonHook(runner, testlog.HCLogger(t))
|
hook := newLogMonHook(runner, testlog.HCLogger(t))
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ func (tr *TaskRunner) initHooks() {
|
||||||
hookLogger := tr.logger.Named("task_hook")
|
hookLogger := tr.logger.Named("task_hook")
|
||||||
task := tr.Task()
|
task := tr.Task()
|
||||||
|
|
||||||
tr.logmonHookConfig = newLogMonHookConfig(task.Name, tr.taskDir.LogDir)
|
tr.logmonHookConfig = newLogMonHookConfig(task.Name, task.LogConfig, tr.taskDir.LogDir)
|
||||||
|
|
||||||
// Add the hook resources
|
// Add the hook resources
|
||||||
tr.hookResources = &hookResources{}
|
tr.hookResources = &hookResources{}
|
||||||
|
|
|
@ -1245,6 +1245,7 @@ func ApiTaskToStructsTask(job *structs.Job, group *structs.TaskGroup,
|
||||||
structsTask.LogConfig = &structs.LogConfig{
|
structsTask.LogConfig = &structs.LogConfig{
|
||||||
MaxFiles: *apiTask.LogConfig.MaxFiles,
|
MaxFiles: *apiTask.LogConfig.MaxFiles,
|
||||||
MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB,
|
MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB,
|
||||||
|
Enabled: *apiTask.LogConfig.Enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(apiTask.Artifacts) > 0 {
|
if len(apiTask.Artifacts) > 0 {
|
||||||
|
@ -1809,6 +1810,7 @@ func apiLogConfigToStructs(in *api.LogConfig) *structs.LogConfig {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &structs.LogConfig{
|
return &structs.LogConfig{
|
||||||
|
Enabled: *in.Enabled,
|
||||||
MaxFiles: dereferenceInt(in.MaxFiles),
|
MaxFiles: dereferenceInt(in.MaxFiles),
|
||||||
MaxFileSizeMB: dereferenceInt(in.MaxFileSizeMB),
|
MaxFileSizeMB: dereferenceInt(in.MaxFileSizeMB),
|
||||||
}
|
}
|
||||||
|
|
|
@ -2767,6 +2767,7 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) {
|
||||||
KillTimeout: pointer.Of(10 * time.Second),
|
KillTimeout: pointer.Of(10 * time.Second),
|
||||||
KillSignal: "SIGQUIT",
|
KillSignal: "SIGQUIT",
|
||||||
LogConfig: &api.LogConfig{
|
LogConfig: &api.LogConfig{
|
||||||
|
Enabled: pointer.Of(true),
|
||||||
MaxFiles: pointer.Of(10),
|
MaxFiles: pointer.Of(10),
|
||||||
MaxFileSizeMB: pointer.Of(100),
|
MaxFileSizeMB: pointer.Of(100),
|
||||||
},
|
},
|
||||||
|
@ -3184,6 +3185,7 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) {
|
||||||
KillTimeout: 10 * time.Second,
|
KillTimeout: 10 * time.Second,
|
||||||
KillSignal: "SIGQUIT",
|
KillSignal: "SIGQUIT",
|
||||||
LogConfig: &structs.LogConfig{
|
LogConfig: &structs.LogConfig{
|
||||||
|
Enabled: true,
|
||||||
MaxFiles: 10,
|
MaxFiles: 10,
|
||||||
MaxFileSizeMB: 100,
|
MaxFileSizeMB: 100,
|
||||||
},
|
},
|
||||||
|
@ -3340,6 +3342,7 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) {
|
||||||
KillTimeout: pointer.Of(10 * time.Second),
|
KillTimeout: pointer.Of(10 * time.Second),
|
||||||
KillSignal: "SIGQUIT",
|
KillSignal: "SIGQUIT",
|
||||||
LogConfig: &api.LogConfig{
|
LogConfig: &api.LogConfig{
|
||||||
|
Enabled: pointer.Of(true),
|
||||||
MaxFiles: pointer.Of(10),
|
MaxFiles: pointer.Of(10),
|
||||||
MaxFileSizeMB: pointer.Of(100),
|
MaxFileSizeMB: pointer.Of(100),
|
||||||
},
|
},
|
||||||
|
@ -3465,6 +3468,7 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) {
|
||||||
KillTimeout: 10 * time.Second,
|
KillTimeout: 10 * time.Second,
|
||||||
KillSignal: "SIGQUIT",
|
KillSignal: "SIGQUIT",
|
||||||
LogConfig: &structs.LogConfig{
|
LogConfig: &structs.LogConfig{
|
||||||
|
Enabled: true,
|
||||||
MaxFiles: 10,
|
MaxFiles: 10,
|
||||||
MaxFileSizeMB: 100,
|
MaxFileSizeMB: 100,
|
||||||
},
|
},
|
||||||
|
@ -3637,9 +3641,11 @@ func TestConversion_apiLogConfigToStructs(t *testing.T) {
|
||||||
ci.Parallel(t)
|
ci.Parallel(t)
|
||||||
require.Nil(t, apiLogConfigToStructs(nil))
|
require.Nil(t, apiLogConfigToStructs(nil))
|
||||||
require.Equal(t, &structs.LogConfig{
|
require.Equal(t, &structs.LogConfig{
|
||||||
|
Enabled: true,
|
||||||
MaxFiles: 2,
|
MaxFiles: 2,
|
||||||
MaxFileSizeMB: 8,
|
MaxFileSizeMB: 8,
|
||||||
}, apiLogConfigToStructs(&api.LogConfig{
|
}, apiLogConfigToStructs(&api.LogConfig{
|
||||||
|
Enabled: pointer.Of(true),
|
||||||
MaxFiles: pointer.Of(2),
|
MaxFiles: pointer.Of(2),
|
||||||
MaxFileSizeMB: pointer.Of(8),
|
MaxFileSizeMB: pointer.Of(8),
|
||||||
}))
|
}))
|
||||||
|
@ -3737,6 +3743,7 @@ func TestConversion_apiConnectSidecarTaskToStructs(t *testing.T) {
|
||||||
Meta: meta,
|
Meta: meta,
|
||||||
KillTimeout: &timeout,
|
KillTimeout: &timeout,
|
||||||
LogConfig: &structs.LogConfig{
|
LogConfig: &structs.LogConfig{
|
||||||
|
Enabled: true,
|
||||||
MaxFiles: 2,
|
MaxFiles: 2,
|
||||||
MaxFileSizeMB: 8,
|
MaxFileSizeMB: 8,
|
||||||
},
|
},
|
||||||
|
@ -3755,6 +3762,7 @@ func TestConversion_apiConnectSidecarTaskToStructs(t *testing.T) {
|
||||||
Meta: meta,
|
Meta: meta,
|
||||||
KillTimeout: &timeout,
|
KillTimeout: &timeout,
|
||||||
LogConfig: &api.LogConfig{
|
LogConfig: &api.LogConfig{
|
||||||
|
Enabled: pointer.Of(true),
|
||||||
MaxFiles: pointer.Of(2),
|
MaxFiles: pointer.Of(2),
|
||||||
MaxFileSizeMB: pointer.Of(8),
|
MaxFileSizeMB: pointer.Of(8),
|
||||||
},
|
},
|
||||||
|
|
|
@ -253,7 +253,7 @@ func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error {
|
||||||
net: handleState.DriverNetwork,
|
net: handleState.DriverNetwork,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !d.config.DisableLogCollection {
|
if loggingIsEnabled(d.config, handle.Config) {
|
||||||
h.dlogger, h.dloggerPluginClient, err = d.reattachToDockerLogger(handleState.ReattachConfig)
|
h.dlogger, h.dloggerPluginClient, err = d.reattachToDockerLogger(handleState.ReattachConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.logger.Warn("failed to reattach to docker logger process", "error", err)
|
d.logger.Warn("failed to reattach to docker logger process", "error", err)
|
||||||
|
@ -284,6 +284,16 @@ func (d *Driver) RecoverTask(handle *drivers.TaskHandle) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loggingIsEnabled(driverCfg *DriverConfig, taskCfg *drivers.TaskConfig) bool {
|
||||||
|
if driverCfg.DisableLogCollection {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if taskCfg.StderrPath == os.DevNull && taskCfg.StdoutPath == os.DevNull {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) {
|
func (d *Driver) StartTask(cfg *drivers.TaskConfig) (*drivers.TaskHandle, *drivers.DriverNetwork, error) {
|
||||||
if _, ok := d.tasks.Get(cfg.ID); ok {
|
if _, ok := d.tasks.Get(cfg.ID); ok {
|
||||||
return nil, nil, fmt.Errorf("task with ID %q already started", cfg.ID)
|
return nil, nil, fmt.Errorf("task with ID %q already started", cfg.ID)
|
||||||
|
@ -399,7 +409,7 @@ CREATE:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
collectingLogs := !d.config.DisableLogCollection
|
collectingLogs := loggingIsEnabled(d.config, cfg)
|
||||||
|
|
||||||
var dlogger docklog.DockerLogger
|
var dlogger docklog.DockerLogger
|
||||||
var pluginClient *plugin.Client
|
var pluginClient *plugin.Client
|
||||||
|
|
|
@ -133,7 +133,7 @@ func dockerTask(t *testing.T) (*drivers.TaskConfig, *TaskConfig, []int) {
|
||||||
func dockerSetup(t *testing.T, task *drivers.TaskConfig, driverCfg map[string]interface{}) (*docker.Client, *dtestutil.DriverHarness, *taskHandle, func()) {
|
func dockerSetup(t *testing.T, task *drivers.TaskConfig, driverCfg map[string]interface{}) (*docker.Client, *dtestutil.DriverHarness, *taskHandle, func()) {
|
||||||
client := newTestDockerClient(t)
|
client := newTestDockerClient(t)
|
||||||
driver := dockerDriverHarness(t, driverCfg)
|
driver := dockerDriverHarness(t, driverCfg)
|
||||||
cleanup := driver.MkAllocDir(task, true)
|
cleanup := driver.MkAllocDir(task, loggingIsEnabled(&DriverConfig{}, task))
|
||||||
|
|
||||||
copyImage(t, task.TaskDir(), "busybox.tar")
|
copyImage(t, task.TaskDir(), "busybox.tar")
|
||||||
_, _, err := driver.StartTask(task)
|
_, _, err := driver.StartTask(task)
|
||||||
|
@ -841,6 +841,37 @@ func TestDockerDriver_LoggingConfiguration(t *testing.T) {
|
||||||
require.Equal(t, loggerConfig, container.HostConfig.LogConfig.Config)
|
require.Equal(t, loggerConfig, container.HostConfig.LogConfig.Config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestDockerDriver_LogCollectionDisabled ensures that logmon isn't configured
|
||||||
|
// when log collection is disable, but out-of-band Docker log shipping still
|
||||||
|
// works as expected
|
||||||
|
func TestDockerDriver_LogCollectionDisabled(t *testing.T) {
|
||||||
|
ci.Parallel(t)
|
||||||
|
testutil.DockerCompatible(t)
|
||||||
|
|
||||||
|
task, cfg, _ := dockerTask(t)
|
||||||
|
task.StdoutPath = os.DevNull
|
||||||
|
task.StderrPath = os.DevNull
|
||||||
|
|
||||||
|
must.NoError(t, task.EncodeConcreteDriverConfig(cfg))
|
||||||
|
|
||||||
|
dockerClientConfig := make(map[string]interface{})
|
||||||
|
loggerConfig := map[string]string{"gelf-address": "udp://1.2.3.4:12201", "tag": "gelf"}
|
||||||
|
|
||||||
|
dockerClientConfig["logging"] = LoggingConfig{
|
||||||
|
Type: "gelf",
|
||||||
|
Config: loggerConfig,
|
||||||
|
}
|
||||||
|
client, d, handle, cleanup := dockerSetup(t, task, dockerClientConfig)
|
||||||
|
t.Cleanup(cleanup)
|
||||||
|
must.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
|
||||||
|
container, err := client.InspectContainer(handle.containerID)
|
||||||
|
must.NoError(t, err)
|
||||||
|
must.Nil(t, handle.dlogger)
|
||||||
|
|
||||||
|
must.Eq(t, "gelf", container.HostConfig.LogConfig.Type)
|
||||||
|
must.Eq(t, loggerConfig, container.HostConfig.LogConfig.Config)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDockerDriver_HealthchecksDisable(t *testing.T) {
|
func TestDockerDriver_HealthchecksDisable(t *testing.T) {
|
||||||
ci.Parallel(t)
|
ci.Parallel(t)
|
||||||
testutil.DockerCompatible(t)
|
testutil.DockerCompatible(t)
|
||||||
|
|
|
@ -262,6 +262,7 @@ func parseTask(item *ast.ObjectItem, keys []string) (*api.Task, error) {
|
||||||
valid := []string{
|
valid := []string{
|
||||||
"max_files",
|
"max_files",
|
||||||
"max_file_size",
|
"max_file_size",
|
||||||
|
"enabled",
|
||||||
}
|
}
|
||||||
if err := checkHCLKeys(logsBlock.Val, valid); err != nil {
|
if err := checkHCLKeys(logsBlock.Val, valid); err != nil {
|
||||||
return nil, multierror.Prefix(err, "logs ->")
|
return nil, multierror.Prefix(err, "logs ->")
|
||||||
|
|
|
@ -350,6 +350,7 @@ func TestParse(t *testing.T) {
|
||||||
LogConfig: &api.LogConfig{
|
LogConfig: &api.LogConfig{
|
||||||
MaxFiles: intToPtr(14),
|
MaxFiles: intToPtr(14),
|
||||||
MaxFileSizeMB: intToPtr(101),
|
MaxFileSizeMB: intToPtr(101),
|
||||||
|
Enabled: boolToPtr(true),
|
||||||
},
|
},
|
||||||
Artifacts: []*api.TaskArtifact{
|
Artifacts: []*api.TaskArtifact{
|
||||||
{
|
{
|
||||||
|
|
|
@ -194,6 +194,7 @@ job "binstore-storagelocker" {
|
||||||
}
|
}
|
||||||
|
|
||||||
logs {
|
logs {
|
||||||
|
enabled = true
|
||||||
max_files = 14
|
max_files = 14
|
||||||
max_file_size = 101
|
max_file_size = 101
|
||||||
}
|
}
|
||||||
|
|
|
@ -4420,6 +4420,7 @@ func TestTaskDiff(t *testing.T) {
|
||||||
LogConfig: &LogConfig{
|
LogConfig: &LogConfig{
|
||||||
MaxFiles: 1,
|
MaxFiles: 1,
|
||||||
MaxFileSizeMB: 10,
|
MaxFileSizeMB: 10,
|
||||||
|
Enabled: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Expected: &TaskDiff{
|
Expected: &TaskDiff{
|
||||||
|
@ -4429,6 +4430,12 @@ func TestTaskDiff(t *testing.T) {
|
||||||
Type: DiffTypeAdded,
|
Type: DiffTypeAdded,
|
||||||
Name: "LogConfig",
|
Name: "LogConfig",
|
||||||
Fields: []*FieldDiff{
|
Fields: []*FieldDiff{
|
||||||
|
{
|
||||||
|
Type: DiffTypeAdded,
|
||||||
|
Name: "Enabled",
|
||||||
|
Old: "",
|
||||||
|
New: "false",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Type: DiffTypeAdded,
|
Type: DiffTypeAdded,
|
||||||
Name: "MaxFileSizeMB",
|
Name: "MaxFileSizeMB",
|
||||||
|
@ -4452,6 +4459,7 @@ func TestTaskDiff(t *testing.T) {
|
||||||
LogConfig: &LogConfig{
|
LogConfig: &LogConfig{
|
||||||
MaxFiles: 1,
|
MaxFiles: 1,
|
||||||
MaxFileSizeMB: 10,
|
MaxFileSizeMB: 10,
|
||||||
|
Enabled: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
New: &Task{},
|
New: &Task{},
|
||||||
|
@ -4462,6 +4470,12 @@ func TestTaskDiff(t *testing.T) {
|
||||||
Type: DiffTypeDeleted,
|
Type: DiffTypeDeleted,
|
||||||
Name: "LogConfig",
|
Name: "LogConfig",
|
||||||
Fields: []*FieldDiff{
|
Fields: []*FieldDiff{
|
||||||
|
{
|
||||||
|
Type: DiffTypeDeleted,
|
||||||
|
Name: "Enabled",
|
||||||
|
Old: "false",
|
||||||
|
New: "",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Type: DiffTypeDeleted,
|
Type: DiffTypeDeleted,
|
||||||
Name: "MaxFileSizeMB",
|
Name: "MaxFileSizeMB",
|
||||||
|
@ -4485,12 +4499,14 @@ func TestTaskDiff(t *testing.T) {
|
||||||
LogConfig: &LogConfig{
|
LogConfig: &LogConfig{
|
||||||
MaxFiles: 1,
|
MaxFiles: 1,
|
||||||
MaxFileSizeMB: 10,
|
MaxFileSizeMB: 10,
|
||||||
|
Enabled: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
New: &Task{
|
New: &Task{
|
||||||
LogConfig: &LogConfig{
|
LogConfig: &LogConfig{
|
||||||
MaxFiles: 2,
|
MaxFiles: 2,
|
||||||
MaxFileSizeMB: 20,
|
MaxFileSizeMB: 20,
|
||||||
|
Enabled: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Expected: &TaskDiff{
|
Expected: &TaskDiff{
|
||||||
|
@ -4500,6 +4516,12 @@ func TestTaskDiff(t *testing.T) {
|
||||||
Type: DiffTypeEdited,
|
Type: DiffTypeEdited,
|
||||||
Name: "LogConfig",
|
Name: "LogConfig",
|
||||||
Fields: []*FieldDiff{
|
Fields: []*FieldDiff{
|
||||||
|
{
|
||||||
|
Type: DiffTypeEdited,
|
||||||
|
Name: "Enabled",
|
||||||
|
Old: "false",
|
||||||
|
New: "true",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Type: DiffTypeEdited,
|
Type: DiffTypeEdited,
|
||||||
Name: "MaxFileSizeMB",
|
Name: "MaxFileSizeMB",
|
||||||
|
@ -4524,12 +4546,14 @@ func TestTaskDiff(t *testing.T) {
|
||||||
LogConfig: &LogConfig{
|
LogConfig: &LogConfig{
|
||||||
MaxFiles: 1,
|
MaxFiles: 1,
|
||||||
MaxFileSizeMB: 10,
|
MaxFileSizeMB: 10,
|
||||||
|
Enabled: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
New: &Task{
|
New: &Task{
|
||||||
LogConfig: &LogConfig{
|
LogConfig: &LogConfig{
|
||||||
MaxFiles: 1,
|
MaxFiles: 1,
|
||||||
MaxFileSizeMB: 20,
|
MaxFileSizeMB: 20,
|
||||||
|
Enabled: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Expected: &TaskDiff{
|
Expected: &TaskDiff{
|
||||||
|
@ -4539,6 +4563,12 @@ func TestTaskDiff(t *testing.T) {
|
||||||
Type: DiffTypeEdited,
|
Type: DiffTypeEdited,
|
||||||
Name: "LogConfig",
|
Name: "LogConfig",
|
||||||
Fields: []*FieldDiff{
|
Fields: []*FieldDiff{
|
||||||
|
{
|
||||||
|
Type: DiffTypeEdited,
|
||||||
|
Name: "Enabled",
|
||||||
|
Old: "false",
|
||||||
|
New: "true",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Type: DiffTypeEdited,
|
Type: DiffTypeEdited,
|
||||||
Name: "MaxFileSizeMB",
|
Name: "MaxFileSizeMB",
|
||||||
|
|
|
@ -7225,6 +7225,7 @@ const (
|
||||||
type LogConfig struct {
|
type LogConfig struct {
|
||||||
MaxFiles int
|
MaxFiles int
|
||||||
MaxFileSizeMB int
|
MaxFileSizeMB int
|
||||||
|
Enabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LogConfig) Equal(o *LogConfig) bool {
|
func (l *LogConfig) Equal(o *LogConfig) bool {
|
||||||
|
@ -7240,6 +7241,10 @@ func (l *LogConfig) Equal(o *LogConfig) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if l.Enabled != o.Enabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7250,6 +7255,7 @@ func (l *LogConfig) Copy() *LogConfig {
|
||||||
return &LogConfig{
|
return &LogConfig{
|
||||||
MaxFiles: l.MaxFiles,
|
MaxFiles: l.MaxFiles,
|
||||||
MaxFileSizeMB: l.MaxFileSizeMB,
|
MaxFileSizeMB: l.MaxFileSizeMB,
|
||||||
|
Enabled: l.Enabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7258,11 +7264,13 @@ func DefaultLogConfig() *LogConfig {
|
||||||
return &LogConfig{
|
return &LogConfig{
|
||||||
MaxFiles: 10,
|
MaxFiles: 10,
|
||||||
MaxFileSizeMB: 10,
|
MaxFileSizeMB: 10,
|
||||||
|
Enabled: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate returns an error if the log config specified are less than
|
// Validate returns an error if the log config specified are less than the
|
||||||
// the minimum allowed.
|
// minimum allowed. Note that because we have a non-zero default MaxFiles and
|
||||||
|
// MaxFileSizeMB, we can't validate that they're unset if Enabled=false
|
||||||
func (l *LogConfig) Validate() error {
|
func (l *LogConfig) Validate() error {
|
||||||
var mErr multierror.Error
|
var mErr multierror.Error
|
||||||
if l.MaxFiles < 1 {
|
if l.MaxFiles < 1 {
|
||||||
|
|
|
@ -188,7 +188,17 @@ FieldsLoop:
|
||||||
ObjectsLoop:
|
ObjectsLoop:
|
||||||
for _, oDiff := range diff.Objects {
|
for _, oDiff := range diff.Objects {
|
||||||
switch oDiff.Name {
|
switch oDiff.Name {
|
||||||
case "LogConfig", "Service", "Constraint":
|
case "Service", "Constraint":
|
||||||
|
continue
|
||||||
|
case "LogConfig":
|
||||||
|
for _, fDiff := range oDiff.Fields {
|
||||||
|
switch fDiff.Name {
|
||||||
|
// force a destructive update if logger was enabled or disabled
|
||||||
|
case "Enabled":
|
||||||
|
destructive = true
|
||||||
|
break ObjectsLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
destructive = true
|
destructive = true
|
||||||
|
|
|
@ -336,6 +336,27 @@ func TestAnnotateTask(t *testing.T) {
|
||||||
Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited},
|
Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited},
|
||||||
Desired: AnnotationForcesInplaceUpdate,
|
Desired: AnnotationForcesInplaceUpdate,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Diff: &structs.TaskDiff{
|
||||||
|
Type: structs.DiffTypeEdited,
|
||||||
|
Objects: []*structs.ObjectDiff{
|
||||||
|
{
|
||||||
|
Type: structs.DiffTypeAdded,
|
||||||
|
Name: "LogConfig",
|
||||||
|
Fields: []*structs.FieldDiff{
|
||||||
|
{
|
||||||
|
Type: structs.DiffTypeAdded,
|
||||||
|
Name: "Enabled",
|
||||||
|
Old: "true",
|
||||||
|
New: "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Parent: &structs.TaskGroupDiff{Type: structs.DiffTypeEdited},
|
||||||
|
Desired: AnnotationForcesDestructiveUpdate,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Diff: &structs.TaskDiff{
|
Diff: &structs.TaskDiff{
|
||||||
Type: structs.DiffTypeEdited,
|
Type: structs.DiffTypeEdited,
|
||||||
|
|
|
@ -304,6 +304,13 @@ func tasksUpdated(jobA, jobB *structs.Job, taskGroup string) comparison {
|
||||||
if !at.Identity.Equal(bt.Identity) {
|
if !at.Identity.Equal(bt.Identity) {
|
||||||
return difference("task identity", at.Identity, bt.Identity)
|
return difference("task identity", at.Identity, bt.Identity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Most LogConfig updates are in-place but if we change Enabled we need
|
||||||
|
// to recreate the task to stop/start log collection and change the
|
||||||
|
// stdout/stderr of the task
|
||||||
|
if at.LogConfig.Enabled != bt.LogConfig.Enabled {
|
||||||
|
return difference("task log enabled", at.LogConfig.Enabled, bt.LogConfig.Enabled)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// none of the fields that trigger a destructive update were modified,
|
// none of the fields that trigger a destructive update were modified,
|
||||||
|
|
|
@ -603,7 +603,8 @@ fields:
|
||||||
|
|
||||||
## Stream Logs
|
## Stream Logs
|
||||||
|
|
||||||
This endpoint streams a task's stderr/stdout logs.
|
This endpoint streams a task's stderr/stdout logs. Note that if logging is set
|
||||||
|
to [enabled=false][] for the task, this endpoint will return a 404 error.
|
||||||
|
|
||||||
| Method | Path | Produces |
|
| Method | Path | Produces |
|
||||||
| ------ | ------------------------------ | ------------ |
|
| ------ | ------------------------------ | ------------ |
|
||||||
|
@ -832,3 +833,4 @@ $ nomad operator api /v1/client/gc
|
||||||
```
|
```
|
||||||
|
|
||||||
[api-node-read]: /nomad/api-docs/nodes
|
[api-node-read]: /nomad/api-docs/nodes
|
||||||
|
[enabled=false]: /nomad/docs/job-specification/logs#enabled
|
||||||
|
|
|
@ -491,6 +491,7 @@ $ curl \
|
||||||
},
|
},
|
||||||
"KillTimeout": 5000000000,
|
"KillTimeout": 5000000000,
|
||||||
"LogConfig": {
|
"LogConfig": {
|
||||||
|
"Enabled": true,
|
||||||
"MaxFiles": 10,
|
"MaxFiles": 10,
|
||||||
"MaxFileSizeMB": 10
|
"MaxFileSizeMB": 10
|
||||||
},
|
},
|
||||||
|
@ -762,6 +763,7 @@ $ curl \
|
||||||
"Leader": false,
|
"Leader": false,
|
||||||
"Lifecycle": null,
|
"Lifecycle": null,
|
||||||
"LogConfig": {
|
"LogConfig": {
|
||||||
|
"Enabled": true,
|
||||||
"MaxFileSizeMB": 10,
|
"MaxFileSizeMB": 10,
|
||||||
"MaxFiles": 10
|
"MaxFiles": 10
|
||||||
},
|
},
|
||||||
|
@ -979,6 +981,7 @@ $ curl \
|
||||||
"Leader": false,
|
"Leader": false,
|
||||||
"Lifecycle": null,
|
"Lifecycle": null,
|
||||||
"LogConfig": {
|
"LogConfig": {
|
||||||
|
"Enabled": true,
|
||||||
"MaxFileSizeMB": 10,
|
"MaxFileSizeMB": 10,
|
||||||
"MaxFiles": 10
|
"MaxFiles": 10
|
||||||
},
|
},
|
||||||
|
@ -1145,6 +1148,7 @@ $ curl \
|
||||||
"Leader": false,
|
"Leader": false,
|
||||||
"Lifecycle": null,
|
"Lifecycle": null,
|
||||||
"LogConfig": {
|
"LogConfig": {
|
||||||
|
"Enabled": true,
|
||||||
"MaxFileSizeMB": 10,
|
"MaxFileSizeMB": 10,
|
||||||
"MaxFiles": 10
|
"MaxFiles": 10
|
||||||
},
|
},
|
||||||
|
|
|
@ -927,6 +927,8 @@ The `Affinity` object supports the following keys:
|
||||||
The `LogConfig` object configures the log rotation policy for a task's `stdout` and
|
The `LogConfig` object configures the log rotation policy for a task's `stdout` and
|
||||||
`stderr`. The `LogConfig` object supports the following attributes:
|
`stderr`. The `LogConfig` object supports the following attributes:
|
||||||
|
|
||||||
|
- `Enabled` - Enables log collection.
|
||||||
|
|
||||||
- `MaxFiles` - The maximum number of rotated files Nomad will retain for
|
- `MaxFiles` - The maximum number of rotated files Nomad will retain for
|
||||||
`stdout` and `stderr`, each tracked individually.
|
`stdout` and `stderr`, each tracked individually.
|
||||||
|
|
||||||
|
@ -940,6 +942,7 @@ a validation error when a job is submitted.
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"LogConfig": {
|
"LogConfig": {
|
||||||
|
"Enabled": true,
|
||||||
"MaxFiles": 3,
|
"MaxFiles": 3,
|
||||||
"MaxFileSizeMB": 10
|
"MaxFileSizeMB": 10
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ layout: docs
|
||||||
page_title: logs Block - Job Specification
|
page_title: logs Block - Job Specification
|
||||||
description: |-
|
description: |-
|
||||||
The "logs" block configures the log rotation policy for a task's stdout and
|
The "logs" block configures the log rotation policy for a task's stdout and
|
||||||
stderr. Logging is enabled by default with sane defaults. The "logs" block
|
stderr. Logging is enabled by default with reasonable defaults. The "logs" block
|
||||||
allows for finer-grained control over how Nomad handles log files.
|
allows for finer-grained control over how Nomad handles log files.
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -12,10 +12,9 @@ description: |-
|
||||||
<Placement groups={['job', 'group', 'task', 'logs']} />
|
<Placement groups={['job', 'group', 'task', 'logs']} />
|
||||||
|
|
||||||
The `logs` block configures the log rotation policy for a task's `stdout` and
|
The `logs` block configures the log rotation policy for a task's `stdout` and
|
||||||
`stderr`. Logging is enabled by default with sane defaults (provided in the
|
`stderr`. Logging is enabled by default with reasonable defaults (provided in
|
||||||
parameters section below), and there is currently no way to disable logging for
|
the parameters section below). The `logs` block allows for finer-grained control
|
||||||
tasks. The `logs` block allows for finer-grained control over how Nomad handles
|
over how Nomad handles log files.
|
||||||
log files.
|
|
||||||
|
|
||||||
Nomad's log rotation works by writing stdout/stderr output from tasks to a file
|
Nomad's log rotation works by writing stdout/stderr output from tasks to a file
|
||||||
inside the `alloc/logs/` directory with the following format:
|
inside the `alloc/logs/` directory with the following format:
|
||||||
|
@ -32,6 +31,7 @@ job "docs" {
|
||||||
logs {
|
logs {
|
||||||
max_files = 10
|
max_files = 10
|
||||||
max_file_size = 10
|
max_file_size = 10
|
||||||
|
enabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,17 @@ please see the [`nomad alloc logs`][logs-command] command.
|
||||||
the total amount of disk space needed to retain the rotated set of files,
|
the total amount of disk space needed to retain the rotated set of files,
|
||||||
Nomad will return a validation error when a job is submitted.
|
Nomad will return a validation error when a job is submitted.
|
||||||
|
|
||||||
|
- `enabled` `(bool: true)` - Specifies that log collection should be enabled for
|
||||||
|
this task. If set to `false`, the task driver will attach stdout/stderr of the
|
||||||
|
task to `/dev/null` (or `NUL` on Windows). You should only disable log
|
||||||
|
collection if your application has some other way of emitting logs, such as
|
||||||
|
writing to a remote syslog server. Note that the `nomad alloc logs` command
|
||||||
|
and related APIs will return errors (404 "not found") if logging is disabled.
|
||||||
|
|
||||||
|
Some task drivers such as `docker` support a [`disable_log_collection`][]
|
||||||
|
option. If the task driver's `disable_log_collection` option is set to `true`,
|
||||||
|
it will override `enabled=true` in the task's `logs` block.
|
||||||
|
|
||||||
## `logs` Examples
|
## `logs` Examples
|
||||||
|
|
||||||
The following examples only show the `logs` blocks. Remember that the
|
The following examples only show the `logs` blocks. Remember that the
|
||||||
|
@ -60,7 +71,7 @@ The following examples only show the `logs` blocks. Remember that the
|
||||||
### Configure Defaults
|
### Configure Defaults
|
||||||
|
|
||||||
This example shows a default logging configuration. Yes, it is empty on purpose.
|
This example shows a default logging configuration. Yes, it is empty on purpose.
|
||||||
Nomad automatically enables logging with sane defaults as described in the
|
Nomad automatically enables logging with reasonable defaults as described in the
|
||||||
parameters section above.
|
parameters section above.
|
||||||
|
|
||||||
```hcl
|
```hcl
|
||||||
|
@ -81,3 +92,4 @@ logs {
|
||||||
```
|
```
|
||||||
|
|
||||||
[logs-command]: /nomad/docs/commands/alloc/logs 'Nomad logs command'
|
[logs-command]: /nomad/docs/commands/alloc/logs 'Nomad logs command'
|
||||||
|
[`disable_log_collection`]: /nomad/docs/drivers/docker#disable_log_collection
|
||||||
|
|
Loading…
Reference in a new issue