github.com/smithx10/nomad@v0.9.1-rc1/client/allocrunner/taskrunner/logmon_hook.go (about) 1 package taskrunner 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "path/filepath" 8 "runtime" 9 10 hclog "github.com/hashicorp/go-hclog" 11 plugin "github.com/hashicorp/go-plugin" 12 "github.com/hashicorp/nomad/client/allocrunner/interfaces" 13 "github.com/hashicorp/nomad/client/logmon" 14 "github.com/hashicorp/nomad/helper/uuid" 15 "github.com/hashicorp/nomad/nomad/structs" 16 pstructs "github.com/hashicorp/nomad/plugins/shared/structs" 17 ) 18 19 const ( 20 // logmonReattachKey is the HookData key where logmon's reattach config 21 // is stored. 22 logmonReattachKey = "reattach_config" 23 ) 24 25 // logmonHook launches logmon and manages task logging 26 type logmonHook struct { 27 // logmon is the handle to the log monitor process for the task. 28 logmon logmon.LogMon 29 logmonPluginClient *plugin.Client 30 31 config *logmonHookConfig 32 33 logger hclog.Logger 34 } 35 36 type logmonHookConfig struct { 37 logDir string 38 stdoutFifo string 39 stderrFifo string 40 } 41 42 func newLogMonHook(cfg *logmonHookConfig, logger hclog.Logger) *logmonHook { 43 hook := &logmonHook{ 44 config: cfg, 45 logger: logger, 46 } 47 48 return hook 49 } 50 51 func newLogMonHookConfig(taskName, logDir string) *logmonHookConfig { 52 cfg := &logmonHookConfig{ 53 logDir: logDir, 54 } 55 if runtime.GOOS == "windows" { 56 id := uuid.Generate()[:8] 57 cfg.stdoutFifo = fmt.Sprintf("//./pipe/%s-%s.stdout", taskName, id) 58 cfg.stderrFifo = fmt.Sprintf("//./pipe/%s-%s.stderr", taskName, id) 59 } else { 60 cfg.stdoutFifo = filepath.Join(logDir, fmt.Sprintf(".%s.stdout.fifo", taskName)) 61 cfg.stderrFifo = filepath.Join(logDir, fmt.Sprintf(".%s.stderr.fifo", taskName)) 62 } 63 return cfg 64 } 65 66 func (*logmonHook) Name() string { 67 return "logmon" 68 } 69 70 func (h *logmonHook) launchLogMon(reattachConfig *plugin.ReattachConfig) error { 71 l, c, err := logmon.LaunchLogMon(h.logger, reattachConfig) 72 if err != nil { 73 return err 74 } 75 76 h.logmon = l 77 h.logmonPluginClient = c 78 return nil 79 } 80 81 func reattachConfigFromHookData(data map[string]string) (*plugin.ReattachConfig, error) { 82 if data == nil || data[logmonReattachKey] == "" { 83 return nil, nil 84 } 85 86 var cfg pstructs.ReattachConfig 87 err := json.Unmarshal([]byte(data[logmonReattachKey]), &cfg) 88 if err != nil { 89 return nil, err 90 } 91 92 return pstructs.ReattachConfigToGoPlugin(&cfg) 93 } 94 95 func (h *logmonHook) Prestart(ctx context.Context, 96 req *interfaces.TaskPrestartRequest, resp *interfaces.TaskPrestartResponse) error { 97 98 // Attempt to reattach to logmon 99 if h.logmonPluginClient == nil { 100 reattachConfig, err := reattachConfigFromHookData(req.PreviousState) 101 if err != nil { 102 h.logger.Error("failed to load reattach config", "error", err) 103 return err 104 } 105 if reattachConfig != nil { 106 if err := h.launchLogMon(reattachConfig); err != nil { 107 h.logger.Warn("failed to reattach to logmon process", "error", err) 108 } 109 } 110 111 } 112 113 // We did not reattach to a plugin and one is still not running. 114 if h.logmonPluginClient == nil || h.logmonPluginClient.Exited() { 115 if err := h.launchLogMon(nil); err != nil { 116 // Retry errors launching logmon as logmon may have crashed on start and 117 // subsequent attempts will start a new one. 118 h.logger.Error("failed to launch logmon process", "error", err) 119 return structs.NewRecoverableError(err, true) 120 } 121 } 122 123 err := h.logmon.Start(&logmon.LogConfig{ 124 LogDir: h.config.logDir, 125 StdoutLogFile: fmt.Sprintf("%s.stdout", req.Task.Name), 126 StderrLogFile: fmt.Sprintf("%s.stderr", req.Task.Name), 127 StdoutFifo: h.config.stdoutFifo, 128 StderrFifo: h.config.stderrFifo, 129 MaxFiles: req.Task.LogConfig.MaxFiles, 130 MaxFileSizeMB: req.Task.LogConfig.MaxFileSizeMB, 131 }) 132 if err != nil { 133 h.logger.Error("failed to start logmon", "error", err) 134 return err 135 } 136 137 rCfg := pstructs.ReattachConfigFromGoPlugin(h.logmonPluginClient.ReattachConfig()) 138 jsonCfg, err := json.Marshal(rCfg) 139 if err != nil { 140 return err 141 } 142 resp.State = map[string]string{logmonReattachKey: string(jsonCfg)} 143 return nil 144 } 145 146 func (h *logmonHook) Stop(_ context.Context, req *interfaces.TaskStopRequest, _ *interfaces.TaskStopResponse) error { 147 148 // It's possible that Stop was called without calling Prestart on agent 149 // restarts. Attempt to reattach to an existing logmon. 150 if h.logmon == nil || h.logmonPluginClient == nil { 151 if err := h.reattach(req); err != nil { 152 h.logger.Trace("error reattaching to logmon when stopping", "error", err) 153 } 154 } 155 156 if h.logmon != nil { 157 h.logmon.Stop() 158 } 159 if h.logmonPluginClient != nil { 160 h.logmonPluginClient.Kill() 161 } 162 163 return nil 164 } 165 166 // reattach to a running logmon if possible. Will not start a new logmon. 167 func (h *logmonHook) reattach(req *interfaces.TaskStopRequest) error { 168 reattachConfig, err := reattachConfigFromHookData(req.ExistingState) 169 if err != nil { 170 return err 171 } 172 173 // Give up if there's no reattach config 174 if reattachConfig == nil { 175 return nil 176 } 177 178 return h.launchLogMon(reattachConfig) 179 }